Build a Serverless REST API on AWS
Wire Lambda, API Gateway, DynamoDB, and SNS into a live CRUD API.
Introduction
30 Second Summary
Every time you add a task to a to-do app or place an order online, something happens behind the scenes. Data gets saved and notifications get sent automatically, without any human pressing buttons.
In this project, you will build an event-driven serverless task management API on AWS. You will wire together five cloud services so you can create, read, update, and delete tasks through a live URL, with automatic email notifications every time a new task is created.
What You'll Build
From your terminal, you fire off a single command to a live URL, a task appears in a cloud database, and seconds later a notification email lands in your inbox.
By the end of this project, you'll have:
- A live REST API endpoint you can hit from any terminal with curl to create, read, update, and delete tasks in a cloud database.
- Automatic email notifications that arrive in your inbox every time a task is created, powered by Lambda and SNS.
- A working IAM permission chain connecting Lambda, API Gateway, DynamoDB, and SNS with least-privilege policies.
- Secret Mission: Add a "list all tasks" endpoint and a scan operation to your API.
Are there any prerequisites?
You need an AWS account (Free Tier or paid plan). All services used in this project fall within the AWS Free Tier, so you will not be charged.
You also need curl, which comes pre-installed on macOS.
Before We Start
Before diving into the AWS console, let's lock in what you're about to build and why it matters.
Set Up IAM Permissions and DynamoDB
You know what you're building. Now it's time to lay the foundation that every other step depends on.
Your Lambda function needs permission to access DynamoDB and write logs. Without an IAM policy and execution role, the function cannot touch any AWS resource. Without a table, there is nothing to operate on. Setting up the security and data layer first means every subsequent step can run immediately.
In this step, get ready to:
- Create a custom IAM policy that grants DynamoDB and CloudWatch Logs permissions.
- Create an IAM execution role that Lambda will assume, with the policy attached.
- Create a DynamoDB table to store your tasks.
Create a custom IAM policy
An IAM policy is a JSON document that defines what actions a service is allowed to perform. Your Lambda function needs two sets of permissions: one for DynamoDB operations and one for writing logs to CloudWatch.
- Sign in to the AWS Management Console.
- In the top search bar, type IAM and select IAM from the results.
- In the left navigation pane, click Policies.
- Click Create policy.
- Select the JSON tab in the policy editor.
- Delete any existing content in the editor, then paste the following policy JSON:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1428341300017",
"Action": [
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:UpdateItem"
],
"Effect": "Allow",
"Resource": "*"
},
{
"Sid": "",
"Resource": "*",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Effect": "Allow"
}
]
}
What does this policy do?
This policy has two permission blocks (called statements).
- The first statement grants six DynamoDB actions: DeleteItem, GetItem, PutItem, Query, Scan, and UpdateItem. These cover all the CRUD operations your Lambda function will perform.
- The second statement grants three CloudWatch Logs actions so Lambda can write execution logs. Without these, you would have no way to debug your function.
Why does this policy use Resource "*"?
In production, you would scope the Resource field to the specific table ARN and log group ARN. For this learning project, the wildcard keeps things simple while you focus on understanding how the services connect.
- Click Next.
- For Policy name, enter lambda-apigateway-policy.
- Click Create policy.
You should see a success banner confirming that the policy lambda-apigateway-policy has been created.
Seeing a JSON syntax error?
- Make sure you deleted all the default content in the editor before pasting. Leftover characters cause parsing errors.
- Confirm you are on the JSON tab, not the visual editor.
Help me fix a JSON syntax error in my IAM policy
Create an IAM execution role
A policy defines what actions are allowed. A role defines who is allowed to perform them. You need an execution role that Lambda can assume, with your new policy attached.
- In the IAM console left navigation pane, click Roles.
- Click Create role.
- For the trusted entity type, select AWS service.
- For the use case, select Lambda.
- Click Next.
- In the policy search box, type lambda-apigateway-policy.
- Select the checkbox next to lambda-apigateway-policy in the search results.
- Click Next.
- For Role name, enter lambda-apigateway-role.
- Click Create role.
You should see a success banner confirming that the role lambda-apigateway-role has been created.
What is an execution role?
An execution role is an IAM role that an AWS service assumes to perform actions on your behalf. By selecting Lambda as the trusted entity, you told AWS that the Lambda service is allowed to "wear" this role. When your function runs, Lambda automatically assumes this role and gains the permissions defined in the attached policy.
Can't find your policy in the search results?
- Make sure you type the exact name: lambda-apigateway-policy. The search is case-sensitive.
- If the policy still does not appear, go back to Policies in the left navigation and confirm it was created successfully in the previous substep.
Help me find my IAM policy when creating a role
Create the DynamoDB table
Your permissions are in place. Now you need the actual database table that your Lambda function will read from and write to.
- In the top search bar, type DynamoDB and select DynamoDB from the results.
- In the left navigation pane, click Tables.
- Click Create table.
- For Table name, enter lambda-apigateway.
- For Partition key, enter id and keep the data type set as String.
- Under Table settings, keep the Default settings.
- Click Create table.
What is a partition key?
A partition key is the primary identifier for each item in a DynamoDB table. Every item must have a unique partition key value. Your table uses id as the partition key, so each task you create will need a unique id value.
Before you check, what status do you expect to see for your new table?
- Wait a few seconds, then look at the Status column on the Tables page.
The lambda-apigateway table should show Status: Active. This confirms your table is ready to receive data.
Table still showing Creating?
- DynamoDB tables typically take 5-10 seconds to become active. Refresh the page if the status has not updated.
- If the table does not appear at all, confirm you clicked Create table and check that you are in the same AWS Region where you created it.
Help me troubleshoot my DynamoDB table creation
Your security layer and data layer are in place. Next up, you will create the Lambda function that uses this role to perform CRUD operations on your table.
Create and Test the Lambda Function
Your permissions and database are locked in. Now it is time to build the brain of your API.
The Lambda function receives an operation name and a payload, then performs the matching CRUD action on DynamoDB. Building and testing it before connecting any API means you can verify the core logic in isolation. If something breaks later, you know it is not the function.
In this step, get ready to:
- Create a Lambda function with the Python 3.13 runtime.
- Test the function with an echo event to prove it runs.
- Test a create operation and confirm the item lands in DynamoDB.
Create the Lambda function
You need a Lambda function that your API will invoke later. For now, you will create it and wire it to the execution role from Step 1.
- Type Lambda in the AWS console search bar at the top and select Lambda from the results.
- Click Create function.
- Select Author from scratch.
- For Function name, enter LambdaFunctionOverHttps.
- For Runtime, choose Python 3.13.
- Under Permissions, expand Change default execution role.
- Select Use an existing role, then choose lambda-apigateway-role from the dropdown.
- Click Create function.
Why use an existing role?
In Step 1 you created lambda-apigateway-role with permissions to read and write to DynamoDB and send logs to CloudWatch. By attaching that role here, your function can talk to DynamoDB the moment it runs.
Lambda creates the function with a default "Hello world" handler. You will replace that code with the CRUD handler next.
Add the CRUD handler code
Your function needs to route five operations to the matching DynamoDB call. You will paste the handler into the Lambda console editor in two parts.
- In the Code source pane, select all the default code in lambda_function.py and delete it.
- Paste the following code to set up the DynamoDB connection and define the CRUD functions:
import boto3
# Define the DynamoDB table that Lambda will connect to
table_name = "lambda-apigateway"
# Create the DynamoDB resource
dynamo = boto3.resource('dynamodb').Table(table_name)
# Define functions to perform the CRUD operations
def create(payload):
return dynamo.put_item(Item=payload['Item'])
def read(payload):
return dynamo.get_item(Key=payload['Key'])
def update(payload):
return dynamo.update_item(**{k: payload[k] for k in ['Key', 'UpdateExpression',
'ExpressionAttributeNames', 'ExpressionAttributeValues'] if k in payload})
def delete(payload):
return dynamo.delete_item(Key=payload['Key'])
def echo(payload):
return payload
What does this code do?
- boto3 is the AWS SDK for Python. It is included in the Lambda runtime, so no install is needed.
- dynamo connects to your lambda-apigateway table using the high-level Table resource.
- Each function maps to one DynamoDB operation: put_item, get_item, update_item, and delete_item.
- echo simply returns whatever you send it. This is useful for testing the function without touching DynamoDB.
- Directly below the code you just pasted, add the operations dictionary and handler function:
operations = {
'create': create,
'read': read,
'update': update,
'delete': delete,
'echo': echo,
}
def lambda_handler(event, context):
'''Provide an event that contains the following keys:
- operation: one of the operations in the operations dict below
- payload: a JSON object containing parameters to pass to the
operation being performed
'''
operation = event['operation']
payload = event['payload']
if operation in operations:
return operations[operation](payload)
else:
raise ValueError(f'Unrecognized operation "{operation}"')
What does this code do?
- operations is a dictionary that maps string names like "create" to the matching function. This avoids a long if/elif chain.
- lambda_handler is the entry point Lambda calls every time the function is invoked. It reads the operation and payload from the incoming event, looks up the right function, and calls it.
- If someone sends an operation name that does not exist, the function raises a clear error.
- In the DEPLOY section, click Deploy to save your function code.
You should see a banner confirming the function was updated successfully.
Don't see the Deploy button?
Make sure you are on the Code tab for your function. The Deploy button appears in the code editor area, not on other tabs.
help me find the Deploy button in the Lambda console
✔️ Awesome, I've got everything!
Great. Make sure you have saved your code by clicking Deploy.
ⓧ I'd like to double check the full code
Scroll back up through the two code blocks you pasted. Confirm your file contains these sections in order:
- The import boto3 line, followed by table_name and dynamo setup.
- Five function definitions: create, read, update, delete, echo.
- The operations dictionary mapping all five names to their functions.
- The lambda_handler function that reads operation and payload from the event.
Test the function with an echo event
Before testing against DynamoDB, you will send a simple echo event. This proves the function runs and the routing logic works without touching the database.
- Click the Test tab on your function page.
- Scroll down to the Event JSON section and replace the default event with the following:
{
"operation": "echo",
"payload": {
"somekey1": "somevalue1"
}
}
- Click Test.
Under Executing function: succeeded, expand Details. You should see your payload echoed back:
{
"somekey1": "somevalue1"
}
Why test echo first?
The echo operation returns whatever you send without calling DynamoDB. If this test fails, you know the problem is in the function code itself, not in the database connection or IAM permissions.
Seeing an error instead of the payload?
- Make sure you clicked Deploy before running the test. Undeployed code changes are not active.
- Check that your JSON matches the format above exactly. A missing comma or bracket causes a parse error.
- Confirm the operations dictionary includes 'echo': echo.
help me debug my Lambda echo test failure
Test a create operation and verify in DynamoDB
The echo test proved your function runs. Now you will send a real create operation that writes an item to DynamoDB and confirm it arrived.
- Stay on the Test tab. Replace the echo event JSON with the following create event:
{
"operation": "create",
"payload": {
"Item": {
"id": "1234ABCD",
"number": 5
}
}
}
- Click Test.
You should see Executing function: succeeded again. The response shows DynamoDB metadata confirming the write.
Now verify the item actually landed in your table.
- Type DynamoDB in the AWS console search bar and select DynamoDB.
- Click Tables in the left sidebar, then select lambda-apigateway.
- Click Explore table items.
Before you look at the results, what do you expect to see in the table?
You should see an item with id set to 1234ABCD and number set to 5. Your Lambda function successfully wrote to DynamoDB.
Don't see the item in the table?
- Confirm the create test returned Executing function: succeeded in the Lambda console. If it failed, check the error message in the response.
- Make sure you are looking at the lambda-apigateway table, not a different table.
- Verify that the lambda-apigateway-role has the lambda-apigateway-policy attached. Without DynamoDB permissions, the function cannot write.
help me figure out why my Lambda create test did not write to DynamoDB
Your Lambda function is tested and working. Next, you will wire up API Gateway so the outside world can call this function over HTTP.
Wire Up API Gateway
Your Lambda function works perfectly, but right now only you can invoke it from the Lambda console. To let the outside world talk to it, you need a public HTTP endpoint.
That is exactly what API Gateway does. In this step, you will create a REST API that exposes your Lambda function as a POST method. This is the same pattern used in real serverless production systems.
In this step, get ready to:
- Create a REST API in API Gateway with a resource and POST method.
- Connect the POST method to your Lambda function.
- Test the integration directly from the API Gateway console.
Create a REST API
An API is the front door for external traffic to reach your Lambda function. You will create one in the API Gateway console now.
- Open the API Gateway console.
- Click Create API.
- Find the REST API box (not the "REST API Private" box) and click Build.
- Under API details, leave New API selected.
- For API name, enter DynamoDBOperations.
- Keep API endpoint type set to Regional.
- For IP address type, select IPv4.
- Click Create API.
You should now see the Resources page for your new API, with a single root resource / listed.
What is a REST API?
A REST API organizes your backend into resources (like URLs) and methods (like GET, POST, PUT, DELETE). Each resource-method combination maps to a specific action in your backend.
In this project, you will create one resource (DynamoDBManager) with a POST method that sends requests to your Lambda function.
Create a resource and POST method
Your API needs a resource to organize incoming requests. Then you will attach a POST method to that resource and point it at your Lambda function.
- On the Resources page, click Create resource.
- For Resource name, enter DynamoDBManager.
- Click Create resource.
You should see /DynamoDBManager appear in the resource tree on the left.
- Make sure the /DynamoDBManager resource is highlighted in the resource tree.
- In the Methods pane on the right, click Create method.
- For Method type, select POST.
- For Integration type, leave Lambda function selected.
- For Lambda function, search for and select LambdaFunctionOverHttps.
- Click Create method.
What about the permission prompt?
You may see a dialog asking you to give API Gateway permission to invoke your Lambda function. Click OK to allow it.
This automatically adds a resource-based policy to your Lambda function so API Gateway can call it.
You should see the POST method appear under /DynamoDBManager in the resource tree. The method details pane shows the Lambda integration pointing to LambdaFunctionOverHttps.
Don't see your Lambda function in the dropdown?
Make sure you are in the same AWS Region where you created LambdaFunctionOverHttps. Check the Region selector in the top-right corner of the console.
Help me find my Lambda function in API Gateway
Test the POST method from the API Gateway console
Before deploying your API to the outside world, you can test it right from the API Gateway console. This sends a request through the full integration path without needing a public URL.
- On the Resources page, click the POST method under /DynamoDBManager.
- Click the Test tab. You may need to click the right arrow button to reveal it.
- Leave Query strings and Headers empty.
- In the Request body field, paste the following JSON:
{
"operation": "create",
"payload": {
"Item": {
"id": "TEST-API",
"number": 99
}
}
}
What does this request do?
This JSON tells your Lambda function to run the create operation. It will insert a new item with id set to TEST-API and number set to 99 into your DynamoDB table.
Before you click Test, what do you think the status code will be if the request reaches your Lambda function successfully?
- Click Test.
You should see a status of 200 in the results. This confirms that API Gateway successfully invoked your Lambda function, which wrote the item to DynamoDB.
Seeing a non-200 status?
If you see status 500, check that your Lambda function code was deployed correctly in the previous step. Return to the Lambda console, open LambdaFunctionOverHttps, and make sure you clicked Deploy after pasting the code.
If you see status 403, API Gateway may not have permission to invoke your function. Delete the POST method, recreate it, and click OK on the permission dialog.
Help me troubleshoot my API Gateway test
Now verify the item landed in your DynamoDB table.
- Open the Tables page of the DynamoDB console.
- Click the lambda-apigateway table.
- Click Explore table items.
In the Items returned pane, you should see two items: the original 1234ABCD from Step 2 and the new TEST-API item with number 99.
Your Lambda function now has a proper API in front of it. Next up, you will deploy this API to a public URL and call it from your terminal using curl.
Deploy and Invoke Your API from the Terminal
In the last step, you tested your API from inside the API Gateway console. That proves the wiring between API Gateway and Lambda works.
The console test tab is an internal tool. To let the outside world talk to your API, you need to deploy it to a stage. Deploying creates a real public URL that any HTTP client can reach.
In this step, get ready to:
- Deploy your REST API to a public stage.
- Create a new task in DynamoDB using curl from your terminal.
- Read the task back to confirm end-to-end connectivity.
Deploy the API to a stage
Right now, your API only exists as a draft inside the API Gateway console. To create a live URL, you deploy the API to a named stage. A stage is like a labeled snapshot of your API configuration.
- Return to the API Gateway console. Select your DynamoDBOperations API.
- On the Resources page, click Deploy API.
- For Stage, choose *New stage*.
- For Stage name, enter test.
- Click Deploy.
You should see a Stage details pane appear with an Invoke URL.
- Copy the Invoke URL and save it somewhere you can easily paste it. You will use this URL for every curl command in this step.
What is a stage?
A stage is a named reference to a deployment of your API. Think of it as a version label. The test stage you just created gives your API a live URL that ends in /test.
In production systems, teams often create multiple stages like dev, staging, and prod to separate testing from live traffic.
Why do I need to deploy?
Changes you make in the API Gateway console are like editing a draft. They are invisible to the outside world until you deploy. If you add a new method or change an integration later, you will need to redeploy for those changes to take effect at the invoke URL.
Create a task from the terminal with curl
Your API is live. Time to prove it by sending a real HTTP request from outside the AWS console.
- Open your terminal. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal and press Enter to open it.
- Create a new item in your DynamoDB table by running this command:
curl [[INVOKE_URL="your Invoke URL, e.g. https://abc123.execute-api.us-east-1.amazonaws.com/test"]]/DynamoDBManager \
-H "Content-Type: application/json" \
-d '{"operation": "create", "payload": {"Item": {"id": "CURL-001", "number": 42}}}'
What does this command do?
- curl sends an HTTP request to your API's public URL. The /DynamoDBManager path matches the resource you created in API Gateway.
- -H "Content-Type: application/json" tells the server you are sending JSON data.
- -d sends the JSON body containing the create operation and the item payload. This triggers your Lambda function's create handler, which calls put_item on DynamoDB.
You should see a JSON response containing "HTTPStatusCode": 200 in the output. That confirms the item was created successfully.
Seeing "Missing Authentication Token" or a 403 error?
- Make sure your URL includes /DynamoDBManager at the end. Hitting just the stage URL without the resource path returns this error.
- Double-check that you pasted the full Invoke URL from the Stage details pane. It should start with https:// and end with /test.
- Confirm there is no extra space between the Invoke URL and /DynamoDBManager.
Help me debug my curl command for API Gateway.
Read the task back to verify the data
Creating an item is only half the story. Reading it back proves the entire round trip works: your terminal sends a request to API Gateway, API Gateway invokes Lambda, Lambda queries DynamoDB, and the data travels all the way back.
Before you run the next command, what do you think the response will contain?
- Read the item you just created by running this command:
curl [[INVOKE_URL="your Invoke URL, e.g. https://abc123.execute-api.us-east-1.amazonaws.com/test"]]/DynamoDBManager \
-H "Content-Type: application/json" \
-d '{"operation": "read", "payload": {"Key": {"id": "CURL-001"}}}'
The response should contain "id": "CURL-001" and "number": 42 inside an Item object. That confirms your serverless API can create and read data through a live public URL.
What does the full response look like?
The Python Lambda runtime wraps the item in a ResponseMetadata object that includes the HTTP status code, headers, and retry info. The important part is the Item field containing your data.
- Open the DynamoDB console. Select the lambda-apigateway table and click Explore table items.
- Confirm the item with id CURL-001 and number 42 appears alongside the two items from earlier steps (1234ABCD and TEST-API).
Don't see CURL-001 in the table?
- Click the refresh icon on the Explore table items page. New items do not always appear instantly.
- Re-run the create curl command and check the terminal output for errors. A 200 status code means the write succeeded.
- Verify you are looking at the correct table (lambda-apigateway) in the correct region.
Help me find my CURL-001 item in DynamoDB.
Your serverless backend is officially live. Anyone with the URL can create and read tasks in your database. Next up, you will add real-time email notifications so you know the moment a new task arrives.
Add SNS Email Notifications
Your API works end-to-end. You can create, read, update, and delete tasks from any terminal. But right now, nobody knows when data changes.
Adding SNS creates an event-driven notification layer. Every time the Lambda function creates a task, it publishes a message to an SNS topic, and subscribers get an email. This is the same architectural pattern behind alerting systems, order confirmations, and audit trails in production AWS environments.
In this step, get ready to:
- Create an SNS topic and subscribe your email address to it.
- Update the IAM policy and Lambda function to enable SNS publishing.
- Test the full notification pipeline from terminal to inbox.
Create an SNS topic and subscribe your email
An SNS topic is a communication channel. You publish messages to a topic, and every subscriber to that topic receives the message automatically. You need one to send email notifications when tasks are created.
- In the AWS Management Console, search for SNS in the top search bar and open the Simple Notification Service console.
- In the left navigation pane, choose Topics.
- Click Create topic.
- For Type, select Standard.
- For Name, enter TaskNotifications.
- Scroll down and click Create topic.
The topic detail page opens. You can see the topic's ARN in the Details section. It follows the format arn:aws:sns:<region>:<account-id>:TaskNotifications. Copy this ARN and save it somewhere. You will need it in a few minutes.
What is an ARN?
An ARN (Amazon Resource Name) is a unique identifier for any resource in AWS. Every Lambda function, DynamoDB table, IAM role, and SNS topic has one. When your Lambda function publishes to SNS, it uses the topic's ARN to target the correct destination.
Now subscribe your email address so you receive notifications when messages are published to this topic.
- On the TaskNotifications topic detail page, click Create subscription.
- For Protocol, choose Email.
- For Endpoint, enter your email address.
- Click Create subscription.
Confirm your subscription
Check your email inbox for a message from AWS Notifications. Open the email and click Confirm subscription. Your browser opens a confirmation page.
You will not receive any SNS notifications until you confirm. If you do not see the email, check your spam folder.
- Return to the SNS console. On the Subscriptions page, refresh the list. Your subscription's Status should now show Confirmed.
Subscription still pending?
If your status still shows Pending confirmation, check your spam or junk mail folder. The confirmation email comes from AWS Notifications and may be filtered. Click the confirmation link in the email, then refresh the SNS console.
Help me troubleshoot my SNS email subscription confirmation
Update the IAM policy to allow SNS publishing
Your Lambda function's code already includes an SNS publish call, but the lambda-apigateway-role does not have permission to use SNS yet. You need to add sns:Publish to the lambda-apigateway-policy.
- In the AWS Management Console, search for IAM and open the IAM console.
- In the left navigation pane, choose Policies.
- Search for lambda-apigateway-policy and click on its name to open it.
- Choose the Permissions tab, then click Edit.
- Choose the JSON option to open the JSON editor.
- Replace the entire policy JSON with the following updated version that adds SNS permissions:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1428341300017",
"Action": [
"dynamodb:DeleteItem",
"dynamodb:GetItem",
"dynamodb:PutItem",
"dynamodb:Query",
"dynamodb:Scan",
"dynamodb:UpdateItem"
],
"Effect": "Allow",
"Resource": "*"
},
{
"Sid": "",
"Resource": "*",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Effect": "Allow"
},
{
"Sid": "Stmt1428341300019",
"Action": [
"sns:Publish"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
What changed in this policy?
You added a third statement to the policy's Statement array. The first two statements are unchanged and still grant DynamoDB and CloudWatch Logs permissions.
The new statement grants sns:Publish permission on all resources. This allows your Lambda function to publish messages to any SNS topic in your account.
- Click Next.
- Review the policy summary, then click Save changes.
The IAM console displays a confirmation banner that the policy has been updated.
Policy validation error?
Make sure you have matching curly braces and square brackets. Each statement in the Statement array must be separated by a comma. A missing comma between the second and third statement objects is the most common cause.
Help me fix my IAM policy JSON
Update the Lambda function with your SNS topic ARN
Your Lambda function code already has the SNS publish logic, but it uses a placeholder ARN. You need to replace it with the real ARN of your TaskNotifications topic.
- In the AWS Management Console, search for Lambda and open the Lambda console.
- Click on LambdaFunctionOverHttps to open it.
- In the Code source editor, find this line:
SNS_TOPIC_ARN = "arn:aws:sns:us-east-1:your-account-id:TaskNotifications"
- Replace the entire string value with your actual topic ARN that you copied earlier. Your line should look something like:
SNS_TOPIC_ARN = "arn:aws:sns:us-east-1:123456789012:TaskNotifications"
Where do I find my topic ARN?
If you did not save your ARN earlier, go back to the SNS console, click Topics in the left pane, and click on TaskNotifications. The ARN is displayed in the Details section. It includes your region and account ID.
- Click Deploy in the Code source section to save and deploy your updated function.
You should see a green banner confirming the function was updated successfully.
Don't see the Deploy button?
Make sure you are in the Code tab of your Lambda function. The Deploy button appears above the code editor. If it is greyed out, you may not have made any changes to the code yet.
Help me deploy my updated Lambda function
Test the notification pipeline
Everything is wired up. Your Lambda function has SNS permission, knows the correct topic ARN, and your email is subscribed. Time to test the full pipeline from terminal to inbox.
Before you run this, what do you think will happen? Will you receive an email, and if so, what will it contain?
- Switch back to your terminal and send a create request by running this command (replace the URL with your Invoke URL from earlier):
curl [[INVOKE_URL="your API invoke URL"]]/test/DynamoDBManager \
-H "Content-Type: application/json" \
-d '{"operation": "create", "payload": {"Item": {"id": "SNS-001", "number": 77}}}'
Within seconds, check your email inbox. You should receive an email from AWS Notifications with a subject line of New Task Created. The body contains a JSON message showing the task details, including the id and number you just created.
What just happened?
- Your curl command hit the API Gateway endpoint, which invoked the Lambda function.
- The Lambda function wrote the new item to DynamoDB with put_item.
- Immediately after, the function published a message to your TaskNotifications SNS topic using sns.publish().
- SNS delivered that message to your email subscription.
No email received?
- Verify your SNS subscription status is Confirmed in the SNS console. Unconfirmed subscriptions do not receive messages.
- Check that the SNS_TOPIC_ARN in your Lambda code matches the ARN shown on your topic's detail page exactly. A mismatched region or account ID causes a silent failure.
- Confirm you clicked Deploy after updating the ARN. Undeployed changes do not take effect.
Help me troubleshoot my SNS email notifications
You have built a complete event-driven notification pipeline. Five AWS services are now working together: API Gateway receives the request, Lambda processes it, DynamoDB stores the data, SNS publishes a notification, and your email inbox receives it. Next up, a secret mission to extend your API even further.
Secret mission
Add a List All Tasks Endpoint
Your API handles individual tasks, but what about seeing everything at once? In this secret mission, add a scan operation that returns every task in your DynamoDB table with a single GET request.
Clean Up Your Resources
Clean Up Your Resources
Decide whether to keep your resources running, pause them to come back later, or delete them entirely. All six resources in this project fall within the AWS Free Tier, so there are no immediate charges for leaving them running.
Cost warning
Lambda, DynamoDB, SNS, and IAM are always-free tier services at this project's usage level. API Gateway's free tier (1 million API calls per month) expires after 12 months. If you no longer need these resources after that period, delete them to avoid any charges.
Resources you used:
- IAM Policy: lambda-apigateway-policy (free).
- IAM Role: lambda-apigateway-role (free).
- DynamoDB Table: lambda-apigateway (free tier: 25 GB storage, 25 RCU, 25 WCU).
- Lambda Function: LambdaFunctionOverHttps (free tier: 1M requests/month, 400,000 GB-seconds/month).
- API Gateway REST API: DynamoDBOperations (free tier: 1M API calls/month for 12 months).
- SNS Topic: TaskNotifications (free tier: 1M publishes/month, 1,000 email deliveries/month).
Keep everything running
No action needed. Choose this if you are still actively building or experimenting with your serverless API.
- All resources stay within the AWS Free Tier for learning and experimentation.
- Lambda, DynamoDB, SNS, and IAM have always-free tiers at this project's usage level.
- API Gateway's free tier covers 1 million calls per month for 12 months from account creation.
Pause - I'll come back to this later
Since all resources are serverless, they only incur usage when invoked. There is nothing actively "running" to shut down.
- Your Lambda function only runs when a request hits the API endpoint. No requests means no compute cost.
- DynamoDB charges are based on read/write capacity and storage. Your free tier covers this project's data easily.
- To prevent accidental API calls, you can delete the test stage in API Gateway. This removes the public URL while keeping your API configuration intact.
Delete - I don't want to use this again
Remove all project resources and start fresh. Follow each step below in order.
Delete the Lambda function
- Open the Functions page in the Lambda console.
- Select LambdaFunctionOverHttps.
- Choose Actions, then Delete.
- Type confirm in the text field and choose Delete.
Delete the API Gateway REST API
- Open the APIs page in the API Gateway console.
- Select DynamoDBOperations.
- Choose Actions, then Delete.
- Choose Delete to confirm.
Delete the DynamoDB table
- Open the Tables page in the DynamoDB console.
- Select the lambda-apigateway table.
- Choose Delete.
- Enter delete in the text box and choose Delete table.
Delete the SNS topic
- Open the Topics page in the SNS console.
- Select the TaskNotifications topic.
- Choose Delete.
- Enter delete me in the confirmation field and choose Delete.
Delete the IAM role and policy
- Open the Roles page in the IAM console.
- Select lambda-apigateway-role.
- Choose Delete.
- Enter the role name in the text field and choose Delete.
- Now navigate to the Policies page in the IAM console.
- Search for lambda-apigateway-policy and select it.
- Choose Actions, then Delete.
- Confirm the deletion by choosing Delete.
Nice Work!
Nice Work!
You just built a fully deployed, event-driven serverless API on AWS. From a single curl command in your terminal, you can create, read, update, and delete tasks in a live NoSQL database, and get an email notification the moment a new task lands.
You've learned how to:
- Built a serverless REST API with API Gateway and Lambda that performs full CRUD operations against a DynamoDB table with persistent data.
- Deployed a public API endpoint to a live stage and invoked it from the terminal using curl, proving end-to-end connectivity from an HTTP request to a NoSQL database.
- Wired up SNS email notifications to create an event-driven alerting pipeline, and configured IAM policies with least-privilege permissions across five AWS services.
- Secret Mission: Extended the API with a list-all-tasks endpoint using a DynamoDB Scan operation and a new GET method.
Ready to quiz yourself?