In modern applications, automated notifications are essential for user engagement. Sending an SMS is a direct and effective way to reach your customers. This guide will walk you through a secure, automated, and serverless solution for sending SMS messages using Twilio, running the code on AWS Lambda, and managing the infrastructure with Terraform.
We'll stick to a key security principle: never hardcode your credentials. We'll see how to pass sensitive information like your Twilio accountSid
and authToken
securely from GitHub Secrets all the way to your Lambda function's environment.
The AWS Lambda Function
First, let's look at the Node.js/TypeScript code that will run on AWS Lambda. This function is responsible for the actual sending of the SMS.
The magic happens in this simple script which uses the official twilio
SDK.
import twilio from 'twilio';
const accountSid = process.env.TWILIO_ACCOUNT_SID;
const authToken = process.env.TWILIO_AUTH_TOKEN;
const from = process.env.TWILIO_FROM; // Your Twilio phone number
// A crucial check to ensure the function has the credentials it needs
if (!accountSid || !authToken || !from) {
throw new Error('Twilio configuration is missing in environment variables.');
}
const client = twilio(accountSid, authToken);
export async function sendTextMessage(recipient: string, message: string) {
try {
const response = await client.messages.create({
body: message,
from: from,
to: recipient,
});
console.log('Message sent successfully:', response.sid);
return response;
} catch (error) {
console.error('Failed to send message:', error);
throw error;
}
}
Takeaways:
- No Hardcoded Secrets: Notice how
accountSid
,authToken
, and thefrom
number are retrieved fromprocess.env
. This is the standard way to access environment variables in Node.js. It keeps your sensitive data out of your source code. - Error Handling: The code gracefully handles potential failures by wrapping the API call in a
try...catch
block. - Initialization: It performs a startup check to ensure the necessary environment variables are present. If not, the function will fail fast, which is better than failing silently later.
The Infrastructure: Defining the Lambda with Terraform
Now, how do we get those environment variables into our Lambda function? We define them using Infrastructure as Code (IaC) with Terraform. This allows us to version control our cloud setup.
The Terraform configuration below defines the Lambda function and, most importantly, injects our variables into its runtime environment.
module "lambda_function_send_notification" {
source = "./lambda-function"
function_name = "${var.component}_${var.env}_send_notification"
role_arn = aws_iam_role.iam_lambda_role.arn
env_vars = local.env_vars # This is where the magic happens!
# ... other configurations
}
locals {
env_vars = {
TWILIO_ACCOUNT_SID = var.TWILIO_ACCOUNT_SID # Mapping Terraform var to Lambda env var
TWILIO_AUTH_TOKEN = var.TWILIO_AUTH_TOKEN # Mapping Terraform var to Lambda env var
TWILIO_FROM = var.TWILIO_FROM # Add your Twilio 'from' number here
# ... other variables
}
}
# Variable definitions to receive values from the CI/CD pipeline
variable "TWILIO_ACCOUNT_SID" {
type = string
}
variable "TWILIO_AUTH_TOKEN" {
type = string
}
variable "TWILIO_FROM" {
type = string
}
Takeaways:
-
env_vars
Block: Thelocals.env_vars
map is the crucial link. It defines the key-value pairs that will become the environment variables inside the AWS Lambda function. - Variable Mapping: We map the incoming Terraform variables (e.g.,
var.TWILIO_ACCOUNT_SID
) to the names our JavaScript code expects (e.g.,TWILIO_ACCOUNT_SID
). This keeps the code clean and decouples it from the naming conventions used in your infrastructure or CI/CD system.
The Automation: CI/CD with GitHub Actions
Finally, let's tie it all together. The GitHub Actions workflow automates the deployment. This is where we securely pull the actual secret values from GitHub Secrets and pass them to our Terraform plan.
name: Deploy
on:
push:
workflow_dispatch:
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
env:
# These TF_VAR_ prefixes tell Terraform to populate variables
TF_VAR_TWILIO_ACCOUNT_SID: ${{ secrets.TWILIO_ACCOUNT_SID }}
TF_VAR_TWILIO_AUTH_TOKEN: ${{ secrets.TWILIO_AUTH_TOKEN }}
TF_VAR_TWILIO_FROM: ${{ secrets.NAVIEN_TWILIO_FROM }} # Added the from number
# ... other environment variables
steps:
- name: Checkout
uses: actions/checkout@v4
# ... build and test steps ...
- name: Set up Terraform
uses: hashicorp/setup-terraform@v3
- name: Terraform Init
working-directory: ./terraforms
run: terraform init
- name: Terraform Plan
working-directory: ./terraforms
run: terraform plan -input=false
- name: Terraform Apply
working-directory: ./terraforms
if: github.ref == 'refs/heads/main'
run: terraform apply -auto-approve -input=false
Takeaways:
- GitHub Secrets: The actual credentials (
${{ secrets.TWILIO_ACCOUNT_SID }}
) are stored securely in your GitHub repository's settings. They are never printed in logs. -
TF_VAR_
Prefix: TheTF_VAR_
prefix is a special convention used by Terraform. When GitHub Actions sets an environment variable likeTF_VAR_TWILIO_ACCOUNT_SID
, Terraform automatically uses its value for theTWILIO_ACCOUNT_SID
input variable in your.tf
files.
The Complete Secure Flow
Here’s the full journey of your secret credentials, from storage to execution, without ever being exposed in your code:
- Storage: Your Twilio Account SID, Auth Token, and phone number are stored as GitHub Secrets.
- CI/CD Pipeline: The GitHub Actions workflow reads the secrets and exports them as environment variables prefixed with
TF_VAR_
. - Infrastructure as Code: Terraform reads the
TF_VAR_
variables and uses them to populate thevariable
blocks in its configuration. - Lambda Environment: Terraform then passes these values into the
environment variables
section of the AWS Lambda function resource. - Execution: When the Lambda function runs, the Node.js code accesses these variables via
process.env
to securely authenticate with the Twilio API.
By following this pattern, you build a system that is not only automated and scalable but also secure by design. ✨
Top comments (0)