AppSync is AWS’ GraphQL offering – and it is an absolutely excellent service for building serverless applications because of the power of AppSync resolvers – which are extremely flexible integrating with Dynamodb and Lambda and allows for easy implementation of complex GraphQL schema operations.
Twilio is a great SMS texting service that is very fast and easy to setup. AWS Amplify client side libraries make connecting to AppSync for operations and real time subscriptions trivial.
So using all the 3 – AppSync Twilio Amplify – to build a serverless text messaging system makes a lot of sense. This guide will do just that, using Cloudformation entirely for the serverless backend. The finished application will look like the feature image above.
Outlining the SMS Texting Application
- The (rudimentary) front end using the Amplify client library subscribes to AppSync for updates and sends the sms details to AppSync.
- AppSync itself is configured with a Lambda Authorizer accepting a simple token for access and invokes a pipeline resolver to do 2 operations in the pipeline:
- Use a Dynamodb resolver to save the request.
- Use a Lambda resolver to send the text message to Twilio using the Twilio SDK and will include the callback url for Twilio to respond to with status updates.
- The Twilio callback is routed to another Lambda for handling which will first do the authentication to make sure it is a valid call from Twilio and then trigger a notification to AppSync by invoking a mutation with AppSync’s NONE data source.
- The status is logged on the front end.
A couple of points to note before we begin.
- We will not focus time explaining GraphQL schemas and such per se, but following the guide should provide enough background to get jumpstarted with working with AppSync for other needs.
- This guide will only include a rudimentary front end of a create-react-app with how to configure Amplify with AppSync and use to make calls – but the purpose is to extend the implementation as fits your use cases.
With that, lets dive in.
Signing up for a Twilio trial account.
Twilio provides a trial account with a small $ balance that should be more than sufficient for our development purposes. In order to use the Twilio SDK, we will need
- The Twilio Account SID which is visible after you sign up on the Twilio console.
- The Twilio Auth Token

- A Twilio trial number

Save all this information securely and we want to use them as parameters in our cloudformation stack for Lambdas to reference. We will do that next.
Starting the Cloudformation Template yaml and creating a Dynamodb Table to store the SMS details
Lets get started with the cloudformation template.yaml in some location and lets define the Twilio parameters.
Since we are defining the parameters for Twilio – lets go ahead and also create an Auth Token parameter for our AppSync API that we will be getting to.
And it also makes sense to define a Dynamodb Table that we will use to store the sms details – all new requests and the Twilio status. For our needs, a very simple schema with a single “primary_key” should suffice that just needs to a unique string.
Note: This guide uses Python for the Lambda runtimes, but the thing about serverless applications, swpa them as you need or prefer.
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Serverless SMS System with AppSync, Amplify & Twilio
Globals:
Function:
Architectures:
- arm64
Runtime: python3.9
Timeout: 300
Parameters:
TwilioToken:
Type: String
TwilioNumber:
Type: String
TwilioSID:
Type: String
AppsyncToken:
Type: String
Resources:
SMSDynamodb:
Type: AWS::DynamoDB::Table
Properties:
TableName: twilio-sms-table
BillingMode: PAY_PER_REQUEST
AttributeDefinitions:
- AttributeName: primary_key
AttributeType: S
KeySchema:
- AttributeName: primary_key
KeyType: HASH
Understanding how Twilio callbacks can be validated when using API Gateway and Lambda
Want to just note this separately as it is not as simple as checking if Twilio sent an authorization token back in the header and the documentation to deal with API Gateway and AWS Lambda is a little dated on the Twilio docs and I had to spend some time figuring the steps.
The short story without the goriness of how Twilio signs their stuff…
- Grab the “x-twilio-signature” from the Lambda event header.
- Decode the Base64 body which will be the Twilio response in query string form key1=value1& etc.
- Convert the body to a dictionary of those key value pairs
- Initialize Twilio’s RequestValidator with your auth token and pass the x-twilio-signature, the body dictionary and the callback url string you will be giving to Twilio when send a text request to the RequestValidator.
If Twilio’s magic succeeded – it will let you know its a valid request and you can use all the information you need from that body dictionary you created.
For this guide we will only be using MessageStatus.
This is my Python implementation of the twilio-callback-handler Lambda which is going to do only above for now.
import os
from urllib.parse import parse_qs
import base64
from twilio.request_validator import RequestValidator
#We will pass this via the cloudformation yaml - see the next section
auth_token = os.environ['TWILIO_TOKEN']
def callback_handler(event, context):
validator = RequestValidator(auth_token)
# Grab the request body and convert it into a dictionary
# This has both the statuses we need...and will be used to validate the request
body = base64.b64decode(event['body'])
body = body.decode('utf-8')
parsed = parse_qs(body)
params = {}
for key in parsed:
params[key] = parsed[key][0]
is_valid_request = validator.validate(
f"https://{event['headers']['host']}{event['rawPath']}?{event['rawQueryString']}",
params,
event["headers"]["x-twilio-signature"]
)
if is_valid_request:
twilio_status = params["MessageStatus"]
else:
print("Ignoring rogue request...")
Creating the API Gateway for Twilio Callbacks and creating the route to the handler Lambda
Lets extend the yaml to create and API Gateway and have it linked to the Lambda we wrote above.
Lets also give the Dynamodb table name and permissions to the Lambda as we are going to need it eventually. Likewise lets give the AppSync Token now as well which we will use later.
TwilioAPIGateway:
Type: AWS::Serverless::HttpApi
Properties:
Description: API Gateway for Twilio Callbacks
TwilioCallbackHandler:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./twilio-callback-handler/
Handler: twilio-callback-handler.callback_handler
Description: Handler for twilio status callbacks
FunctionName: twilio-callback-handler
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref SMSDynamodb
Environment:
Variables:
TWILIO_TOKEN:
Ref: TwilioToken
SMS_TABLE: !Ref SMSDynamodb
APPSYNC_TOKEN:
Ref: AppsyncToken
Events:
TwilioCallback:
Type: HttpApi
Properties:
ApiId: !Ref TwilioAPIGateway
Path: /twilio-sms-callback
Method: post
PayloadFormatVersion: "2.0"
Creating a Lambda to send SMS requests to Twilio
We want to make sure that we provide the Callback url details to Twilio using the generated value from the API gateway Resource above.
Lets first define it in the yaml and then code the implementation. Note that the 3 arguments to the lambda will be defined in the coming sections.
Also note we will send our Dynamodb “primary_key” to Twilio in the Callback url parameters so that we correllate Twilio callbacks to our specific requests
TwilioSMSTexter:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./twilio-sms-texter/
Handler: twilio-sms-texter.sms_texter
Description: Lambda to send SMS via Twilio
FunctionName: twilio-sms-texter
Environment:
Variables:
TWILIO_SID:
Ref: TwilioSID
TWILIO_TOKEN:
Ref: TwilioToken
TWILIO_NUMBER:
Ref: TwilioNumber
CALLBACK_URL:
Fn::Sub: 'https://${TwilioAPIGateway}.execute-api.${AWS::Region}.${AWS::URLSuffix}/twilio-sms-callback'
import os
from twilio.rest import Client
account_sid = os.environ['TWILIO_SID']
auth_token = os.environ['TWILIO_TOKEN']
twilio_number = os.environ['TWILIO_NUMBER']
twilio_callback_url = os.environ['CALLBACK_URL']
twilio_client = Client(account_sid, auth_token)
def sms_texter(event, context):
# We will be coding AppSync to send these to us
primary_key = event["arguments"]["primary_key"]
to_number = event["arguments"]["to_number"]
sms_text = event["arguments"]["sms_text"]
message = twilio_client.messages.create(
body=sms_text,
from_=twilio_number,
to=to_number,
status_callback=f"{twilio_callback_url}?primary_key={primary_key}"
)
print(message.sid)
Creating the AppSync Lambda Authorizer
Note: This is a rather simple level of authorization – we will have a token which will grant access to our AppSync API. This token will be shared on the client side as well. There are more secure means of authorizing calls like using AWS Cognito, but thats for a separate post in the future.
AppSyncAuthorizer:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./appsync-authorizer/
Handler: appsync-authorizer.auth_handler
Description: Lambda Authorizer for App Sync
FunctionName: appsync-authorizer
Environment:
Variables:
AUTH_TOKEN:
Ref: AppsyncToken
import os
auth_token = os.environ['AUTH_TOKEN']
def auth_handler(event, context):
token = event["authorizationToken"]
if token == auth_token:
auth = True
else:
auth = False
return {
"isAuthorized": auth,
}
Now we are about ready to start creating our AppSync API, but before that we need to deal with IAM housekeeping for our AppSync API.
Creating an AppSync Role and Policy with access to our Lambdas and Dynamodb Table
This will create a policy that will grant AppSync access to the SMS Texting Lambda and the sms Dynamodb table
AppSyncRole:
Type: "AWS::IAM::Role"
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: "Allow"
Principal:
Service:
- "appsync.amazonaws.com"
Action:
- "sts:AssumeRole"
Path: "/"
AppSyncAccessPolicy:
Type: "AWS::IAM::Policy"
Properties:
PolicyName: "AppSyncAccessPolicy"
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: "Allow"
Action: "dynamodb:*"
Resource:
- !GetAtt SMSDynamodb.Arn
- Effect: "Allow"
Action: "lambda:invokeFunction"
Resource:
- !GetAtt TwilioSMSTexter.Arn
Roles:
- Ref: AppSyncRole
Creating the AppSync API and defining the Data Sources
Finally we are ready to define the meat of our application. We will be creating the API and wiring it to our Lambda authorizer. We also have 2 data sources for AppSync.
- The Dynamodb table “SMSDynamodb”
- The “TwilioSMSTexter” Lambda – if you are not familiar with this, basically AppSync allows you to define Lambda as a data source just like you could any regular datastore like Dynamodb or RDS. This allows us to define more complex operations in Lambda that we cannot otherwise do using the Velocity Template Language. In our case – sending an SMS to Twilio.
AppSync:
Type: "AWS::AppSync::GraphQLApi"
Properties:
Name: "twilio-sms-appsync"
AuthenticationType: "AWS_LAMBDA"
LambdaAuthorizerConfig:
AuthorizerResultTtlInSeconds: 0
AuthorizerUri: !GetAtt AppSyncAuthorizer.Arn
AppsyncPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !GetAtt AppSyncAuthorizer.Arn
Action: lambda:InvokeFunction
Principal: appsync.amazonaws.com
SourceAccount: !Ref 'AWS::AccountId'
SourceArn: !GetAtt AppSync.Arn
AppSyncDynamoDataSource:
Type: "AWS::AppSync::DataSource"
Properties:
ApiId: !GetAtt AppSync.ApiId
Name: "AppSyncDynamoDataSource"
Description: "AppSync Dynamodb data source referring to SMSDynamodb"
Type: "AMAZON_DYNAMODB"
ServiceRoleArn: !GetAtt AppSyncRole.Arn
DynamoDBConfig:
TableName: !Ref SMSDynamodb
AwsRegion: !Ref "AWS::Region"
AppSyncLambdaDataSource:
Type: "AWS::AppSync::DataSource"
Properties:
ApiId: !GetAtt AppSync.ApiId
Name: "AppSyncLambdaDataSource"
Description: "AppSync LambdaData Source referring to the TwilioSMSTexter Lambda"
Type: "AWS_LAMBDA"
ServiceRoleArn: !GetAtt AppSyncRole.Arn
LambdaConfig:
LambdaFunctionArn: !GetAtt TwilioSMSTexter.Arn
Defining the AppSync GraphQL schema
As mentioned earlier, this particular guide is not a GraphQL refresher – but it should be easy to follow this to form a base for future implementations or extending the schema to more…like querying the data!
We are creating a simple client side application that will
- Send Texts to a number
- Get Real Time Notifications when the text was delivered.
To do this we need an SMS “type” – a json object really to represent what the client gets back. Two mutations – to support the 2 operations above. And one subscription for the client to subscribe for realt time notifications.
Why two mutations? – because GraphQL subscriptions trigger only on mutations. We need a mutation to send texts and then we want an empty mutation that we can trigger asychronously whenever we get callbacks.
Note: In actuality there is a way we can do this particular implementation with a single mutation, but this implementation gives me a chance to demonstrate how AppSync’s NONE data source can be used to trigger notifications to client side subscriptions. From a performance perspective there is probably no difference either way.
Here is the GraphQL schema based on what we discussed above.
AppSyncGraphQLSchema:
Type: "AWS::AppSync::GraphQLSchema"
Properties:
ApiId: !GetAtt AppSync.ApiId
Definition: |
schema {
mutation: Mutation
subscription: Subscription
}
type SMS {
primary_key: String!
sent_at: AWSDateTime!
to_number: String!
sms_text: String!
twilio_status: String!
}
type Mutation {
sendSMS(
primary_key: String!
to_number: String!
sms_text: String!
): SMS!
triggerOnCallback(
primary_key: String!
sent_at: AWSDateTime!
to_number: String!
sms_text: String!
twilio_status: String!
): SMS!
}
type Subscription {
twilioCallback(primary_key: String!): SMS
@aws_subscribe(mutations: ["triggerOnCallback"])
}
Our type object “SMS” has a key, the sent timestamp which is using the AWSDateTime utility that AppSync provides and removes the conversion goriness from you, the to number, the actual message and the delivery status from Twilio.
The send and trigger operations should be self explanatory – they expect the defined parameters and will return the SMS type back to Amplify on the client side. The !exclamation marks indicate fields are mandatory. In our case, everything is.
The subscription – when use AppSync simply define @aws_subscribe and let AppSync do the magic of wiring it all up. We are expecting a parameter in the subscription – so we can filter on only what we need and not get notified of other text messages.
Defining the SMS Sender Pipeline Resolver and Request/Response Mapping Templates
When the client sends a text, we need to do two things…
- Save the details in Dynamodb which our callback handler can then use to update once it gets a status update.
- Actually send the text to Twilio – providing the event primary_key as a correlation id.
We can do this with a “Pipeline Resolver” – AppSync will run the operations in each defined pipeline step before executing the final response mapping template.
The first pipeline step is of course going to be the save to Dynamodb – but before we even call that we are going to prep the data for the the rest of the steps in the request mapping template. We will generate the timestamp and form the Dynamodb put JSON and even form the complete SMS type json to send back to the client as defined in the schema.
We are making use of $util functions provided by AppSync to create Dynamodb compatible jsons and generate the timestamp. AppSync provides several such utilities.
The other final thing to note in the yaml below – $context.stash. The $context object has all the request context details available to all steps in the pipeline, including the all important “arguments” object. Using $context.stash we can define objects that will be made available down the pipeline all the way to the final response mapping template.
SendSMSResolver:
Type: "AWS::AppSync::Resolver"
DependsOn:
- AppSyncGraphQLSchema
Properties:
ApiId: !GetAtt AppSync.ApiId
TypeName: "Mutation"
FieldName: "sendSMS"
Kind: PIPELINE
PipelineConfig:
Functions:
- !GetAtt WritetoDynamoFunction.FunctionId
- !GetAtt SendToTwilioFunction.FunctionId
RequestMappingTemplate: |
#set( $sent_at = "$util.time.nowISO8601()")
#set( $twilio_status = "Sending")
#set($key_attributes = {})
#set($put_attributes = {})
#set($sms_response = {})
$util.qr($key_attributes.put("primary_key", $util.dynamodb.toString(${context.arguments.primary_key})))
$util.qr($sms_response.put("primary_key", "${context.arguments.primary_key}"))
$util.qr($put_attributes.put("sent_at", $util.dynamodb.toString($sent_at)))
$util.qr($sms_response.put("sent_at", $sent_at))
$util.qr($put_attributes.put("to_number", $util.dynamodb.toString(${context.arguments.to_number})))
$util.qr($sms_response.put("to_number", "${context.arguments.to_number}"))
$util.qr($put_attributes.put("sms_text", $util.dynamodb.toString(${context.arguments.sms_text})))
$util.qr($sms_response.put("sms_text", "${context.arguments.sms_text}"))
$util.qr($put_attributes.put("twilio_status", $util.dynamodb.toString($twilio_status)))
$util.qr($sms_response.put("twilio_status", $twilio_status))
$util.qr($ctx.stash.put("key_attributes", $key_attributes))
$util.qr($ctx.stash.put("put_attributes", $put_attributes))
$util.qr($ctx.stash.put("sms_response", $sms_response))
{}
ResponseMappingTemplate: |
#if($ctx.error)
$util.error($ctx.error.message, $ctx.error.type)
#end
$util.toJson($context.stash.sms_response)
Creating the Pipeline Functions – the Dynamodb and Lambda Resolvers
With everything stashed and ready to use – lets first create the Dynamodb resolver – which is a simple Put operation.
WritetoDynamoFunction:
Type: "AWS::AppSync::FunctionConfiguration"
Properties:
ApiId: !GetAtt AppSync.ApiId
Name: WritetoDynamoFunction
Description: Write SMS Request to the SMS Dynamo Table
DataSourceName: !GetAtt AppSyncDynamoDataSource.Name
FunctionVersion: 2018-05-29
RequestMappingTemplate: |
{
"version" : "2017-02-28",
"operation" : "PutItem",
"key" : $util.toJson($ctx.stash.key_attributes),
"attributeValues" : $util.toJson($ctx.stash.put_attributes)
}
ResponseMappingTemplate: |
#if($ctx.error)
$util.error($ctx.error.message, $ctx.error.type)
#end
#return
And next – the Lambda resolver – which doesn’t use any Velocity Templating language like the above one and simply tells Appsync to invoke our Lambda
SendToTwilioFunction:
Type: "AWS::AppSync::FunctionConfiguration"
Properties:
ApiId: !GetAtt AppSync.ApiId
Name: SendToTwilioFunction
Description: Send an SMS to Twilio via the TwilioSMSTexter Lambda
DataSourceName: !GetAtt AppSyncLambdaDataSource.Name
FunctionVersion: 2018-05-29
Triggering subscriptions on Twilio Callbacks with a NONE Data Source
Lastly – we are going to have our callback Lambda invoke AppSync directly when it gets the final Twilio status – we will show how to do that in code in just a moment. First, we need to define a resolver for our “triggerOnCallback” mutation. We will point this resolver to a NONE data source – because there isn’t going to be any actual data mutations (well there is, but our Lambda will update the status directly in Dynamo without telling AppSync to do it).
We just want AppSync to notify subscribers when our Lambda invokes the mutation.
This is the way to define such a resolver.
NoneDataSource:
Type: "AWS::AppSync::DataSource"
Properties:
ApiId: !GetAtt AppSync.ApiId
Name: "NoneDataSource"
Description: "None data source for Lambda to invoke triggerOnCallback mutation on Twilio callbacks"
Type: "NONE"
ServiceRoleArn: !GetAtt AppSyncRole.Arn
TriggerOnCallbackResolver:
Type: "AWS::AppSync::Resolver"
DependsOn:
- AppSyncGraphQLSchema
Properties:
ApiId: !GetAtt AppSync.ApiId
TypeName: "Mutation"
FieldName: "triggerOnCallback"
DataSourceName: !GetAtt NoneDataSource.Name
RequestMappingTemplate: |
{
"version": "2018-05-29",
"payload": $util.toJson($context.arguments)
}
ResponseMappingTemplate: |
#if($ctx.error)
$util.error($ctx.error.message, $ctx.error.type)
#end
$util.toJson($context.result)
Notice the request/response mapping templates?
RequestMappingTemplate: |
{
"version": "2018-05-29",
"payload": $util.toJson($context.arguments)
}
ResponseMappingTemplate: |
#if($ctx.error)
$util.error($ctx.error.message, $ctx.error.type)
#end
$util.toJson($context.result)
This is just a passthrough resolver – take in the arguments and return the result. Our Lambda will do both.
Updating the Twilio Callback Lambda to trigger the AppSync subscriptions
There are probably serverside GraphQL libraries that could provide some syntactic sugar better, but here is my implementation without any.
Updated YAML
TwilioCallbackHandler:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./twilio-callback-handler/
Handler: twilio-callback-handler.callback_handler
Description: Handler for twilio status callbacks
FunctionName: twilio-callback-handler
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref SMSDynamodb
Environment:
Variables:
TWILIO_TOKEN:
Ref: TwilioToken
SMS_TABLE: !Ref SMSDynamodb
APPSYNC_ENDPOINT: !GetAtt AppSync.GraphQLUrl
APPSYNC_TOKEN:
Ref: AppsyncToken
Events:
TwilioCallback:
Type: HttpApi
Properties:
ApiId: !Ref TwilioAPIGateway
Path: /twilio-sms-callback
Method: post
PayloadFormatVersion: "2.0"
Updated Lambda Code
import os
from urllib.parse import parse_qs
import base64
import boto3
import requests
from twilio.request_validator import RequestValidator
auth_token = os.environ['TWILIO_TOKEN']
dynamodb = boto3.resource("dynamodb")
sms_table = dynamodb.Table(os.environ["SMS_TABLE"])
appsync_endpoint = os.environ["APPSYNC_ENDPOINT"]
appsync_token = os.environ["APPSYNC_TOKEN"]
headers = {
"Content-Type": "application/graphql",
"authorization": appsync_token
}
triggerOnCallback = """mutation triggerOnCallback {
triggerOnCallback(
primary_key: "p_primary_key",
sent_at: "p_sent_at",
to_number: "p_to_number",
sms_text: "p_sms_text",
twilio_status: "p_twilio_status") {
primary_key
sent_at
to_number
sms_text
twilio_status
}
}
"""
def callback_handler(event, context):
validator = RequestValidator(auth_token)
# Grab the request body and convert it into a dictionary
# This has both the statuses we need...and will be used to validate the request
body = base64.b64decode(event['body'])
body = body.decode('utf-8')
parsed = parse_qs(body)
params = {}
for key in parsed:
params[key] = parsed[key][0]
is_valid_request = validator.validate(
f"https://{event['headers']['host']}{event['rawPath']}?{event['rawQueryString']}",
params,
event["headers"]["x-twilio-signature"]
)
if is_valid_request:
twilio_status = params["MessageStatus"]
if twilio_status == "sent":
return
primary_key = event["queryStringParameters"]["primary_key"]
sms = sms_table.update_item(
Key={"primary_key": primary_key},
UpdateExpression="set twilio_status = :twilio_status",
ExpressionAttributeValues={
":twilio_status": twilio_status
},
ReturnValues="ALL_NEW"
)
mutation = triggerOnCallback
mutation = mutation.replace("p_primary_key", sms["Attributes"]["primary_key"])
mutation = mutation.replace("p_sent_at", sms["Attributes"]["sent_at"])
mutation = mutation.replace("p_to_number", sms["Attributes"]["to_number"])
mutation = mutation.replace("p_sms_text", sms["Attributes"]["sms_text"])
mutation = mutation.replace("p_twilio_status", sms["Attributes"]["twilio_status"])
requests.post(appsync_endpoint, headers=headers, json={"query": mutation})
Setting up AWS Amplify in a JavaScript Client
I used a very basic create-react-app and installed the Amplify Library
npm install aws-amplify
To configure Amplify with your endpoint:
import Amplify, { API } from "aws-amplify";
const appsync_config = {
aws_appsync_graphqlEndpoint:"your_endoint via .env",
aws_appsync_region: "your_region via .env",
aws_appsync_authenticationType: "AWS_LAMBDA",
};
Amplify.configure(appsync_config);
const token = "your token via .env";
const sms_subscription_string = `subscription MySubscription {
twilioCallback(primary_key: "pk") {
primary_key
sent_at
sms_text
to_number
twilio_status
}
}`;
const sms_send_string = `mutation MyMutation {
sendSMS(primary_key: "pk", to_number: "12345", sms_text: "Hello Twilio!") {
primary_key
sent_at
sms_text
to_number
twilio_status
}
}`;
//run this to open a subscription
const sms_subscription_function = async () => {
subscription = API.graphql({
query: sms_subscription_string,
authToken: token,
}).subscribe({
next: async (subscriptionData) => {
console.log("incoming...");
console.log(subscriptionData);
},
});
};
//run this send a text
const sms_send_function = async () => {
const send = await API.graphql({
query: sms_send_string,
authToken: token,
});
console.log(send);
};
Concluding
And assuming all went well, you should now be good to Text with Appsync & Twilio.
There are many other ways to extend this base to suit your needs, but one obvious next step would be to define a query to retrieve your data.
This is a just a sample implementation below to scan your entire record set, but I will cover this topic in more detail in the future where we cane xplore things like requesting by timestamp and filtering etc.
SampleQueryResolver:
Type: "AWS::AppSync::Resolver"
Properties:
ApiId: !GetAtt AppSync.ApiId
TypeName: "Query"
FieldName: "getAllSMSData"
DataSourceName: !GetAtt SMSDynamodb.Name
RequestMappingTemplate: |
{
"version" : "2017-02-28",
"operation" : "Scan"
}
ResponseMappingTemplate: |
$util.toJson($ctx.result.items)