code examples
code examples
Send SMS with AWS SNS and RedwoodJS: A Developer Guide
A step-by-step guide on integrating AWS Simple Notification Service (SNS) into a RedwoodJS application to send SMS messages using the AWS SDK v3.
This guide provides a step-by-step walkthrough for integrating AWS Simple Notification Service (SNS) into your RedwoodJS application to send SMS messages. We'll build a simple backend service and GraphQL mutation to trigger SMS sending via the AWS SDK for JavaScript v3.
By the end of this tutorial, you will have a RedwoodJS API capable of sending transactional or promotional SMS messages to valid phone numbers using your AWS account, complete with basic error handling and secure credential management.
Project Overview and Goals
What We're Building
We will add functionality to a standard RedwoodJS application enabling it to send SMS messages via AWS SNS. This involves:
- Setting up AWS credentials and permissions.
- Creating a RedwoodJS service to interact with the AWS SNS API.
- Defining a GraphQL mutation to expose this functionality.
- Implementing basic validation and error handling.
Problem Solved
This guide addresses the common need for applications to send SMS notifications, one-time passcodes (OTPs), alerts, or other text messages directly to users' phones reliably and scalably using cloud infrastructure.
Technologies Used
- RedwoodJS: A full-stack JavaScript/TypeScript framework for the web. We'll use its API structure (GraphQL, services) and conventions.
- Node.js: The runtime environment for the RedwoodJS API side.
- AWS SDK for JavaScript v3 (
@aws-sdk/client-sns): The official AWS library for interacting with SNS from Node.js. - AWS SNS (Simple Notification Service): The AWS managed service used to send messages, including SMS.
- GraphQL: The query language used by RedwoodJS for API interactions.
- Yarn: Package manager for the project.
System Architecture
graph LR
A[User/Client] -- GraphQL Mutation --> B(RedwoodJS GraphQL API);
B -- Calls Service --> C(RedwoodJS SMS Service);
C -- Uses AWS SDK --> D(AWS SNS API);
D -- Sends SMS --> E(User's Phone);Prerequisites
- Node.js (v18 or later recommended)
- Yarn (v1 or v3+)
- RedwoodJS CLI installed (
yarn global add redwoodjs/cli) - An active AWS Account
- Basic familiarity with RedwoodJS, TypeScript, GraphQL, and the AWS Management Console.
1. Setting Up the RedwoodJS Project
First, let's create a new RedwoodJS project if you don't already have one.
# Create a new RedwoodJS app (choose TypeScript)
yarn create redwood-app redwood-sns-sms --typescript
# Navigate into the project directory
cd redwood-sns-smsNext, we need to install the AWS SDK v3 package for SNS within the API workspace.
# Install the AWS SDK SNS client in the API workspace
yarn workspace api add @aws-sdk/client-snsFinally, prepare the environment file to store AWS credentials securely. RedwoodJS automatically loads variables from .env files.
Create a file named `.env` in the root directory of your project:
# .env
# AWS Credentials - Obtain from IAM User setup (Section 2)
AWS_ACCESS_KEY_ID=""YOUR_AWS_ACCESS_KEY_ID""
AWS_SECRET_ACCESS_KEY=""YOUR_AWS_SECRET_ACCESS_KEY""
# AWS Region where SNS SMS is supported and you want to operate
# e.g., us-east-1, us-west-2, ap-southeast-1, eu-west-1
AWS_REGION=""us-east-1""Important: Add `.env` to your `.gitignore` file if it's not already there to prevent accidentally committing your credentials.
2. Integrating with AWS SNS: IAM Setup
Before writing code to interact with AWS, we need an IAM (Identity and Access Management) user with the necessary permissions to send SMS messages via SNS. This guide uses IAM User access keys, suitable for local development or applications running outside AWS.
(Note: If your application will be hosted within AWS, such as on EC2, ECS, or Lambda, consider using IAM Roles instead of IAM Users. IAM Roles provide temporary credentials automatically, avoiding the need to manage long-lived access keys.)
Steps to Create an IAM User
- Navigate to IAM: Log in to your AWS Management Console and navigate to the IAM service.
- Create User:
- In the left navigation pane, click on Users.
- Click the Create user button.
- User name: Enter a descriptive name, e.g.,
redwood-sns-sender. - Provide user access to the AWS Management Console: Leave this unchecked unless you specifically need console access for this user (not required for API access). Click Next.
- Set Permissions:
- Select Attach policies directly.
- In the search box, type
AmazonSNSFullAccess. - Check the box next to the
AmazonSNSFullAccesspolicy.- Security Best Practice: While
AmazonSNSFullAccessis used here for simplicity, it grants broad permissions. For production environments, it is highly recommended to create a custom IAM policy granting only thesns:Publishpermission. This adheres to the principle of least privilege. A minimal policy might look like this:(You can further restrict thejson{ ""Version"": ""2012-10-17"", ""Statement"": [ { ""Effect"": ""Allow"", ""Action"": ""sns:Publish"", ""Resource"": ""*"" } ] }Resourceif needed, e.g., to specific regions or topics, althoughsns:Publishto phone numbers often requiresResource: ""*"").
- Security Best Practice: While
- Click Next.
- Review and Create:
- Review the user details and permissions.
- Click Create user.
- Retrieve Credentials:
- On the success screen, click on the username you just created (e.g.,
redwood-sns-sender). - Go to the Security credentials tab.
- Scroll down to the Access keys section and click Create access key.
- Select Application running outside AWS as the use case (or Command Line Interface (CLI) if appropriate).
- Click Next.
- (Optional) Add a description tag, e.g.,
RedwoodJS SNS Sender Key. - Click Create access key.
- Crucial: This is your only chance to view the Secret access key. Copy both the Access key ID and the Secret access key and paste them into your
`.env`file created in Step 1. Download the.csvfile as a backup if needed, but store it securely.
- On the success screen, click on the username you just created (e.g.,
Environment Variable Explanation
AWS_ACCESS_KEY_ID: The access key ID for the IAM user you created. Identifies the user making the API request.AWS_SECRET_ACCESS_KEY: The secret key associated with the Access Key ID. Used to sign API requests, proving identity. Keep this secret!AWS_REGION: The AWS region where your SNS resources will be managed and from which SMS messages will originate. SNS SMS support varies by region. Check the AWS Region Table under ""SNS"" for SMS support.us-east-1is a common choice. The AWS SDK will automatically use these environment variables if not configured explicitly in the code.
3. Implementing Core Functionality: RedwoodJS Service
Now, let's create the RedwoodJS service that will contain the logic for sending SMS messages.
# Generate the sms service (includes service file and test file)
yarn rw g service sms --testsThis command creates two files:
`api/src/services/sms/sms.ts`: Where our core logic will reside.`api/src/services/sms/sms.test.ts`: For writing unit tests.
Open `api/src/services/sms/sms.ts` and replace its contents with the following:
// api/src/services/sms/sms.ts
import { SNSClient, PublishCommand } from '@aws-sdk/client-sns'
import type { PublishCommandInput, PublishCommandOutput } from '@aws-sdk/client-sns'
import { UserInputError, RedwoodError } from '@redwoodjs/graphql-server'
import { logger } from 'src/lib/logger'
// Instantiate the SNS Client
// The SDK automatically picks up credentials and region from environment variables
// AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION
const snsClient = new SNSClient({}) // No explicit config needed if env vars are set
/**
* Validates if a phone number is potentially in E.164 format.
* Basic check: starts with +, followed by 10-15 digits total after the '+'.
* Note: This is a basic check. For strict validation, consider a dedicated library.
* @param phoneNumber The phone number string to validate.
* @returns boolean True if potentially valid, false otherwise.
*/
const isValidE164 = (phoneNumber: string): boolean => {
// Regex for E.164 format: + followed by 10 to 15 digits.
const pattern = /^\+[1-9]\d{9,14}$/
return typeof phoneNumber === 'string' && pattern.test(phoneNumber)
}
interface SendSmsParams {
to: string
message: string
messageType?: 'Promotional' | 'Transactional' // Optional: Defaults defined by AWS account settings
}
interface SendSmsResult {
success: boolean
messageId?: string
// error field removed as we throw errors instead of returning them in the payload
}
/**
* Sends an SMS message using AWS SNS.
*
* @param to The recipient phone number in E.164 format (e.g., +15551234567).
* @param message The text message body (UTF-8 encoded). Max 1600 bytes for GSM-7, less for UCS-2.
* @param messageType Optional: Specify 'Transactional' for higher delivery reliability (and cost) or 'Promotional' for cost optimization.
* Defaults to your AWS SNS account settings if not provided.
* @returns Promise<Omit<SendSmsResult, 'error'>> An object indicating success, with message ID. Throws on failure.
*/
export const sendSms = async ({
to,
message,
messageType,
}: SendSmsParams): Promise<Omit<SendSmsResult, 'error'>> => {
logger.info({ custom: { to, messageType } }, 'Attempting to send SMS via SNS')
// 1. Input Validation
if (!isValidE164(to)) {
logger.error({ custom: { to } }, 'Invalid E.164 phone number format provided.')
// Throwing UserInputError provides clearer feedback in GraphQL responses
throw new UserInputError('Invalid phone number format. Must be E.164 (e.g., +15551234567).')
}
if (!message || typeof message !== 'string' || message.trim().length === 0) {
logger.error('SMS message body cannot be empty.')
throw new UserInputError('SMS message body cannot be empty.')
}
// 2. Prepare SNS Publish Command Parameters
const params: PublishCommandInput = {
PhoneNumber: to,
Message: message,
}
// Optionally set message type per message (overrides account default)
if (messageType) {
params.MessageAttributes = {
'AWS.SNS.SMS.SMSType': {
DataType: 'String',
StringValue: messageType, // 'Promotional' or 'Transactional'
},
}
}
// 3. Send the message via SNS
try {
const command = new PublishCommand(params)
const data: PublishCommandOutput = await snsClient.send(command)
logger.info(
{ custom: { messageId: data.MessageId, to } },
'SMS sent successfully via SNS'
)
return { success: true, messageId: data.MessageId }
} catch (error) {
logger.error({ custom: { error, to } }, 'Failed to send SMS via SNS')
// Provide generic error to client, log specifics
// Check error type if more specific handling is needed (e.g., throttling)
// RedwoodError is a generic server error for GraphQL
throw new RedwoodError('Failed to send SMS. Please try again later.')
// Throwing is generally preferred for mutations to signal failure clearly in GraphQL
}
}
// Note: The GraphQL resolver logic will be added here automatically by RedwoodJS
// when we define the SDL in the next step.Code Explanation
- Imports: We import necessary components from
`@aws-sdk/client-sns`, RedwoodJS error classes (`UserInputError`,`RedwoodError`), and the RedwoodJS logger. - SNS Client:
`new SNSClient({})`creates an SNS client instance. Because we set the standard AWS environment variables (`AWS_ACCESS_KEY_ID`,`AWS_SECRET_ACCESS_KEY`,`AWS_REGION`), the SDK automatically configures itself. isValidE164: A helper function performs a regular expression check to validate the phone number format required by SNS (starts with+, followed by 10-15 digits). A comment notes its basic nature.sendSmsFunction:- Accepts an object with
to,message, and optionalmessageType. - Input Validation: Checks if the
tonumber appears valid E.164 format and if themessageis not empty. Throws`UserInputError`for invalid inputs, which translates well to GraphQL errors. - Prepare Params: Creates the
`PublishCommandInput`object required by the SDK, includingPhoneNumberandMessage. - Message Type (Optional): If
messageTypeis provided, it addsMessageAttributesto the parameters to specify 'Transactional' or 'Promotional'. Transactional messages generally have higher delivery rates but may cost slightly more. See AWS SNS documentation for details. - Send via SNS: It wraps the
`snsClient.send(command)`call in atry...catchblock. - Success: If successful, logs the success with the returned
MessageIdand returns a success object (without anerrorfield). - Error: If an error occurs, it logs the detailed error and throws a generic
`RedwoodError`to the client, signaling failure via the GraphQL error mechanism.
- Accepts an object with
4. Building the API Layer: RedwoodJS GraphQL
Now, let's expose our `sendSms` service function through the RedwoodJS GraphQL API.
# Generate the GraphQL schema definition language (SDL) file for sms
yarn rw g sdl smsThis creates `api/src/graphql/sms.sdl.ts`. Open this file and define the mutation and its response type:
// api/src/graphql/sms.sdl.ts
export const schema = gql`
type SendSmsResponse {
success: Boolean!
messageId: String
# error field removed - errors are thrown and handled by GraphQL top-level errors
}
type Mutation {
""""""
Sends an SMS message to the specified E.164 phone number via AWS SNS.
""""""
sendSms(
to: String!
message: String!
messageType: String # Optional: 'Transactional' or 'Promotional'
): SendSmsResponse! @requireAuth # Example: Add authentication if needed
# Remove @requireAuth if public access is intended (use with caution!)
}
`SDL Explanation
SendSmsResponseType: Defines the structure of the data returned by the mutation on success. It includes asuccessflag and the optionalmessageIdfrom SNS. Theerrorfield has been removed to align with the service implementation which throws errors.MutationType:- Defines the
`sendSms`mutation. - Specifies input arguments:
to(required String),message(required String), andmessageType(optional String). - Specifies the return type as
SendSmsResponse!(non-nullable on success). @requireAuthDirective: This RedwoodJS directive ensures only authenticated users can call this mutation. Remove or modify this based on your application's authentication needs.
- Defines the
RedwoodJS Magic: Redwood automatically connects the `sendSms` mutation defined in the SDL to the `sendSms` function exported from the corresponding service file (`api/src/services/sms/sms.ts`).
5. Error Handling, Logging, and Retries
Error Handling
- Input Validation:
`UserInputError`in the service provides clear GraphQL client feedback for invalid inputs. - SNS/SDK Errors: Generic
`RedwoodError`is thrown for backend errors, preventing internal detail exposure. Specific AWS SDK error codes can be inspected in thecatchblock for more granular server-side handling if needed. - GraphQL Errors: Redwood translates thrown errors (
`UserInputError`,`RedwoodError`) into standard GraphQL error responses.
Logging
- RedwoodJS's built-in
`logger`(`import { logger } from 'src/lib/logger'`) is used. `logger.info`logs standard operations;`logger.error`logs detailed failure information.- Structured logging (
customproperty) aids analysis in log management systems. Configure logger options in`api/src/lib/logger.ts`.
Retry Mechanisms
- AWS SDK: The SDK v3 has built-in retries for transient errors (e.g., throttling). This is often sufficient.
- Custom Retries: For critical messages or specific errors, implement custom retry logic (e.g., using
`async-retry`) or queue messages for background processing (advanced).
6. Database Schema and Data Layer (Optional Extension)
To log SMS attempts or statuses, you could add a model to `api/db/schema.prisma`:
// api/db/schema.prisma
model SmsLog {
id String @id @default(cuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
to String
message String
messageType String? // 'Promotional' or 'Transactional'
status String // e.g., 'SENT', 'FAILED', 'PENDING'
snsMessageId String? @unique // Store the ID from SNS
errorCode String? // Store error code on failure
errorMessage String? // Store error message on failure
}Migrations
yarn rw prisma migrate dev --name add_sms_logModify the `sendSms` service to create/update `SmsLog` records.
7. Adding Security Features
- Authentication: Use RedwoodJS auth (
`@requireAuth`) to protect the mutation. - Input Validation: Basic E.164 and message checks are implemented. Consider stricter validation or sanitization if needed.
- Credential Security: Use
`.env`locally and secure environment variable management in deployment. Never commit credentials. Ensure`.env`is in`.gitignore`. - Rate Limiting: Implement rate limiting (e.g., via API Gateway, GraphQL Shield, or custom logic) if the endpoint is public or prone to abuse.
- IAM Policy: Use a custom IAM policy with least privilege (
`sns:Publish`only) instead ofAmazonSNSFullAccessin production (see Section 2).
8. Handling Special Cases
- E.164 Format: Strictly enforce the
`+<country_code><number>`format. - Promotional vs. Transactional: Use 'Transactional' for critical messages (e.g., OTPs) for better deliverability; use 'Promotional' for marketing to optimize costs. Set via
`MessageAttributes`. - Message Length & Encoding: Be mindful of SMS character limits (160 GSM-7, 70 UCS-2). SNS handles segmentation but costs may increase.
- Opt-Outs: SNS handles standard opt-outs (STOP). Check status with other SDK commands if needed.
- Sender ID: Custom alphanumeric Sender IDs (e.g., ""MyApp"") can be configured in SNS but may require registration depending on the country.
9. Implementing Performance Optimizations
- SNS Client Reuse: The code correctly reuses a single
`SNSClient`instance. - Batching: SNS
Publishdoesn't batch unique messages to unique phone numbers. For high volume, use parallel async calls (respecting limits) or publish to an SNS Topic if sending the same message to multiple subscribers. - Asynchronous Operations:
async/awaitprevents blocking the Node.js event loop during API calls.
10. Monitoring, Observability, and Analytics
- RedwoodJS Logging: Forward structured logs to a centralized service (CloudWatch Logs, Datadog, etc.).
- AWS CloudWatch Metrics: Monitor SNS metrics (
`NumberOfMessagesPublished`,`NumberOfNotificationsFailed`, etc.) and set alarms. - SNS Delivery Status Logging: Configure SNS to log detailed delivery status to CloudWatch Logs for deep troubleshooting.
- APM Tools: Use tools like Datadog, New Relic, Sentry for tracing application performance.
11. Troubleshooting and Caveats
InvalidParameterValueException: Often due to bad E.164 format or invalid message content.CredentialsError/AuthorizationErrorException: Incorrect/missing AWS keys/region in`.env`or insufficient IAM permissions.- Region Mismatch: Ensure
`.env`AWS_REGIONmatches your intended operational region with SMS support. - SNS Sandbox Limitations: New accounts are sandboxed:
- Spending Limit: Low monthly limit (e.g., $1.00 USD).
- Verified Destinations: May only allow sending to numbers verified in your AWS account.
- Exit Sandbox: You must request removal from the sandbox via an AWS Support case to send to unverified numbers globally and lift the default spending limit. Check the SNS SMS Sandbox documentation.
- DND / Opt-Outs: Delivery may fail to opted-out numbers. 'Transactional' type can help but respect preferences.
- Carrier Filtering: Carriers might filter messages perceived as spam. Use clear content and consider registered Sender IDs.
12. Deployment and CI/CD
- Environment Variables: Securely manage AWS credentials (
`AWS_ACCESS_KEY_ID`,`AWS_SECRET_ACCESS_KEY`,`AWS_REGION`) using your hosting provider's secrets management. Do not commit`.env`. - Deployment Commands: Use standard RedwoodJS commands (
`yarn rw build`,`yarn rw deploy <provider>`). Consult RedwoodJS Deployment Docs. - CI/CD: Integrate build, test (
`yarn rw test api`), and deploy steps. Securely inject environment variables. - Rollback: Understand your provider's rollback procedures.
13. Verification and Testing
Manual Verification
-
Set correct AWS credentials and region in
`.env`. -
Start dev server:
`yarn rw dev`. -
Open GraphQL Playground (
`http://localhost:8911/graphql`). -
Execute the
`sendSms`mutation. Important: Replace`+15551234567`with your own valid E.164 phone number. If you are still in the SNS Sandbox, this number might need to be verified in your AWS account first (see Section 11).graphqlmutation SendTestSms { # Remember to use YOUR valid E.164 number below! sendSms(to: ""+15551234567"", message: ""Hello from RedwoodJS SNS Test!"") { success messageId # No 'error' field here; check top-level errors for failures } } -
Check Playground response for
success: trueand amessageId. -
Verify SMS arrival on the target phone.
-
Check
`yarn rw dev`console logs. -
Test Failure Cases: Try invalid number formats, empty messages, or temporarily invalidate credentials to observe GraphQL error responses.
Automated Testing (Unit Tests)
Use the generated test file (`api/src/services/sms/sms.test.ts`) with SDK mocking.
// api/src/services/sms/sms.test.ts
import { UserInputError, RedwoodError } from '@redwoodjs/graphql-server'
import { sendSms } from './sms'
// Mock the AWS SDK client and commands using Vitest
// Use vi.hoisted for top-level mocks with Vitest (Redwood's test runner)
const mockSend = vi.fn()
vi.mock('@aws-sdk/client-sns', async (importOriginal) => {
const originalModule = await importOriginal()
return {
...originalModule, // Keep other exports if any
SNSClient: vi.fn(() => ({ // Mock the client constructor
send: mockSend, // Provide the mock send function
})),
PublishCommand: vi.fn((input) => ({ // Mock the command constructor
input: input, // Store input for assertion if needed
})),
}
})
describe('sms service', () => {
// Reset mocks before each test
beforeEach(() => {
vi.clearAllMocks()
})
it('sends an SMS successfully with valid inputs', async () => {
const mockMessageId = 'mock-sns-message-id-123'
mockSend.mockResolvedValue({ MessageId: mockMessageId }) // Simulate successful SNS response
const result = await sendSms({
to: '+15551234567',
message: 'Test message',
})
expect(result.success).toBe(true)
expect(result.messageId).toBe(mockMessageId)
expect(mockSend).toHaveBeenCalledTimes(1)
const commandInstance = mockSend.mock.calls[0][0];
expect(commandInstance.input).toEqual({ PhoneNumber: '+15551234567', Message: 'Test message' })
})
it('sends an SMS with Transactional message type', async () => {
mockSend.mockResolvedValue({ MessageId: 'mock-id-trans' })
await sendSms({
to: '+15559876543',
message: 'Transactional Test',
messageType: 'Transactional',
})
expect(mockSend).toHaveBeenCalledTimes(1)
// Check if MessageAttributes were included in the command input
const commandInstance = mockSend.mock.calls[0][0]; // Get the command passed to send
expect(commandInstance.input.MessageAttributes).toEqual({
'AWS.SNS.SMS.SMSType': {
DataType: 'String',
StringValue: 'Transactional',
},
})
})
it('throws UserInputError for invalid E.164 phone number', async () => {
// Test various invalid formats
await expect(sendSms({ to: '12345', message: 'Test' })).rejects.toThrow(UserInputError)
await expect(sendSms({ to: '+123', message: 'Test' })).rejects.toThrow(UserInputError) // Too short
await expect(sendSms({ to: '+12345678901234567', message: 'Test'})).rejects.toThrow(UserInputError) // Too long
await expect(sendSms({ to: '15551234567', message: 'Test'})).rejects.toThrow(UserInputError) // Missing +
// Check specific message
await expect(sendSms({ to: '12345', message: 'Test' })).rejects.toThrow('Invalid phone number format')
expect(mockSend).not.toHaveBeenCalled()
})
it('throws UserInputError for empty message', async () => {
await expect(sendSms({ to: '+15551112222', message: ' ' })).rejects.toThrow(UserInputError)
await expect(sendSms({ to: '+15551112222', message: '' })).rejects.toThrow(UserInputError)
// Check specific message
await expect(sendSms({ to: '+15551112222', message: ' ' })).rejects.toThrow('SMS message body cannot be empty')
expect(mockSend).not.toHaveBeenCalled()
})
it('throws RedwoodError when SNS client fails', async () => {
const snsError = new Error('SNS Failure')
snsError.name = 'SomeSNSError' // Simulate an AWS error name
mockSend.mockRejectedValue(snsError) // Simulate SNS SDK error
await expect(
sendSms({ to: '+15553334444', message: 'Will fail' })
).rejects.toThrow(RedwoodError) // Check for RedwoodError class
// Need to reset mock rejection for the second assertion if using separate awaits
mockSend.mockRejectedValue(snsError); // Re-set the rejection for the next call
await expect(
sendSms({ to: '+15553334444', message: 'Will fail' })
).rejects.toThrow('Failed to send SMS. Please try again later.') // Check for generic message
expect(mockSend).toHaveBeenCalledTimes(2) // Called twice due to two awaits above
})
})Run tests:
yarn rw test apiVerification Checklist
- AWS IAM User created (or Role considered).
- Access Keys/Region stored securely in
`.env`(or Role configured). - Custom IAM policy with
`sns:Publish`recommended/used for production. `@aws-sdk/client-sns`installed inapi.`sms.ts`service created with`sendSms`logic.`sms.sdl.ts`created with`sendSms`mutation (noerrorfield in response type).- Auth (
`@requireAuth`) configured appropriately. - E.164 validation implemented.
- Error handling (
try...catch, logging, throwing`UserInputError`/`RedwoodError`) implemented. - Able to send SMS via GraphQL Playground (respecting Sandbox limits/verification).
- Error cases handled gracefully in GraphQL.
- Unit tests pass (
`yarn rw test api`). - Environment variables configured in deployment.
Final Thoughts
You now have a functional integration for sending SMS messages from your RedwoodJS application using AWS SNS. This provides a scalable and reliable foundation. Remember to refine IAM permissions, monitor usage via CloudWatch, exit the SNS Sandbox for production sending, and enhance features as needed.
Frequently Asked Questions
How to send SMS with RedwoodJS and AWS SNS?
Integrate AWS SNS into your RedwoodJS application by setting up AWS credentials, creating a RedwoodJS service to interact with the AWS SNS API, defining a GraphQL mutation, and implementing error handling. This allows you to send SMS messages via the AWS SDK for JavaScript v3.
What is the AWS SDK for JavaScript v3 used for in RedwoodJS SMS?
The AWS SDK for JavaScript v3, specifically the `@aws-sdk/client-sns` package, is the official AWS library used to interact with the Simple Notification Service (SNS) from your RedwoodJS Node.js backend. It handles the API calls for sending SMS messages.
Why does RedwoodJS use GraphQL for SMS sending?
RedwoodJS uses GraphQL as its query language for API interactions. This allows you to define a `sendSms` mutation in your schema, which then automatically connects to your backend service function, providing a structured and type-safe way to trigger SMS sending.
When should I use 'Transactional' vs 'Promotional' SMS message type?
Use 'Transactional' for critical messages like one-time passcodes (OTPs) due to higher delivery reliability, although it might be slightly more expensive. 'Promotional' is suitable for marketing messages to optimize costs. Set this using the `messageType` parameter in your `sendSms` mutation.
Can I send SMS to unverified numbers with AWS SNS?
New AWS accounts are in a sandbox and may only send to verified numbers. To send to unverified numbers globally, request removal from the sandbox via an AWS Support case. Refer to the AWS SNS SMS Sandbox documentation for details.
How to set up AWS credentials for RedwoodJS SNS integration?
Create an IAM user in the AWS Management Console with the necessary permissions (ideally a custom policy with just `sns:Publish`). Store the access key ID, secret access key, and AWS region in a `.env` file in your project root. Ensure `.env` is in `.gitignore`.
What is the role of a RedwoodJS service in sending SMS?
The RedwoodJS service contains the core logic for sending SMS. It interacts with the AWS SDK, handles input validation, prepares the API request parameters, and manages error responses. In this setup, it's within the `api/src/services/sms/sms.ts` file.
How to handle errors when sending SMS with RedwoodJS and AWS SNS?
The provided code example uses `UserInputError` for invalid inputs (like incorrect phone number format) and `RedwoodError` for backend errors, allowing specific error handling at the service level and translating them to GraphQL errors for the client. For more advanced handling, examine specific AWS SDK error codes within the `catch` block.
How to validate phone numbers for AWS SNS in RedwoodJS?
Use a regular expression or a dedicated library to validate that numbers conform to the E.164 format (+ followed by country code and number) before sending them to the SNS API. This is important to ensure successful SMS delivery.
How can I test my RedwoodJS SMS integration?
Manually test using the GraphQL Playground with your own valid E.164 phone number. Use the provided automated tests in `api/src/services/sms/sms.test.ts`, mocking the AWS SDK for reliable and reproducible testing that doesn't send actual SMS messages.
What are the prerequisites for sending SMS with RedwoodJS and AWS?
You'll need Node.js (v18 or later recommended), Yarn, RedwoodJS CLI, an active AWS Account, and basic familiarity with RedwoodJS, TypeScript, GraphQL, and the AWS Management Console.
How to log SMS sending attempts in RedwoodJS?
Extend your Prisma schema with an `SmsLog` model to record details like recipient, message, status, and any errors. Modify the `sendSms` service to create or update `SmsLog` entries after each attempt.
What is the purpose of the @requireAuth directive in the GraphQL schema?
The `@requireAuth` directive ensures that only authenticated users can call the `sendSms` mutation. If you intend for public access, remove this directive, but proceed with extreme caution to prevent abuse.
How do I handle AWS credentials securely in a deployed RedwoodJS application?
Use your hosting provider's secrets management service to store and inject AWS credentials into your application's environment. Never commit AWS credentials to your code repository.