
Implementing daily SMS sending API call limits with Twilio Functions + Momento
Introduction
Setting limits on the number of SMS sends when operating the Twilio SMS API is an effective measure to prevent cost increases due to unexpected mass sending and abuse by malicious users. In this article, I'll introduce a low-cost solution that combines Twilio Functions and Momento Cache to limit the number of SMS messages sent per day. You can implement a restriction feature that works entirely within the Twilio + Momento serverless environment without requiring complex infrastructure.
About Twilio
Twilio is a cloud platform that provides communication features such as SMS, voice calls, and video calls as APIs. Twilio Functions is a serverless execution environment provided by Twilio that allows you to easily deploy and run Node.js-based code.
About Momento
Momento is a fully managed in-memory cache service. It provides functionality similar to Redis while eliminating the complexity of server management and scaling.
Target Audience
- Developers who want to implement usage limits for SMS sending with Twilio
- Those interested in low-cost cache implementation in a serverless environment
References
System Architecture
- Twilio Functions: Acts as a proxy for SMS sending and checks sending limits
- Momento Cache: Stores the daily send count as a counter
- Twilio Messaging API: Executes the actual SMS sending
Preparation
Twilio Account Setup
Log in to the Twilio Console and note the following information:
- Account SID
- Auth Token
- Phone number for SMS sending
Momento Account Setup
Create an account on the Momento Console and perform the following steps:
-
Create a cache
- Cache name: Any name (e.g.,
sms-limit
)
- Cache name: Any name (e.g.,
-
Create API key
- Create a Fine-grained Access Key
- Grant
readwrite
permission to the created cache
Implementation Steps### Creating Twilio Functions
-
Creating a New Service
- Create a new Service from Functions and Assets > Services in the Twilio Console
-
Adding Dependencies
- Add
@gomomento/sdk
to Dependencies
- Add
-
Storing the Momento Auth Token
Since Twilio Functions environment variables are limited to 255 characters, we'll store the Token as a Private Asset.- Add + > Add Asset(Static text file) to create a new Asset
- Set the filename to
momento-token
- Set Visibility to Private
- Enter the Momento API key you created earlier
- Execute Deploy All to complete the deployment### Implementation of SMS Sending Limit Function
- Add + > Add Asset(Static text file) to create a new Asset
Creating an endpoint /send-sms
that checks daily sending limits before sending an SMS.
/send-sms
// /send-sms
const { CacheClient, Configurations, CredentialProvider } = require('@gomomento/sdk');
exports.handler = async function(context, event, callback) {
try {
// Load Momento Auth Token from Private Asset
const openTokenFile = Runtime.getAssets()['/momento-token'].open;
const authToken = openTokenFile().trim();
// Initialize Momento client
const cacheClient = new CacheClient({
configuration: Configurations.Laptop.v1(),
credentialProvider: CredentialProvider.fromString({
authToken: authToken
}),
defaultTtlSeconds: 25 * 60 * 60 // 25 hours (to ensure coverage of date changes)
});
const cacheName = 'sms-limit';
// Use today's date as the key
const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format
const countKey = `sms_count_${today}`;
// Get current send count
const currentCountResponse = await cacheClient.get(cacheName, countKey);
let currentCount = 0;
if (currentCountResponse.type === 'Hit') {
currentCount = parseInt(currentCountResponse.valueString()) || 0;
}
// Check sending limit (1 per day in this case)
const dailyLimit = 1;
if (currentCount >= dailyLimit) {
const response = new Twilio.Response();
response.setStatusCode(429);
response.setBody(JSON.stringify({
success: false,
error: 'Daily SMS limit exceeded',
currentCount: currentCount,
limit: dailyLimit,
resetTime: `${today}T23:59:59Z`
}));
response.appendHeader('Content-Type', 'application/json');
return callback(null, response);
}
// Get request parameters
const { to, body: messageBody } = event;
if (!to || !messageBody) {
const response = new Twilio.Response();
response.setStatusCode(400);
response.setBody(JSON.stringify({
success: false,
error: 'Missing required parameters: to, body'
}));
response.appendHeader('Content-Type', 'application/json');
return callback(null, response);
}
// Send SMS
const client = context.getTwilioClient();
const message = await client.messages.create({
body: messageBody,
from: context.TWILIO_PHONE_NUMBER, // Set in Environment Variables
to: to
});
// Increment counter after successful sending
await cacheClient.set(cacheName, countKey, (currentCount + 1).toString());
const response = new Twilio.Response();
response.setStatusCode(200);
response.setBody(JSON.stringify({
success: true,
messageSid: message.sid,
currentCount: currentCount + 1,
remainingCount: dailyLimit - (currentCount + 1)
}));
response.appendHeader('Content-Type', 'application/json');
callback(null, response);
} catch (error) {
console.error('Error:', error);
const response = new Twilio.Response();
response.setStatusCode(500);
response.setBody(JSON.stringify({
success: false,
error: error.message
}));
response.appendHeader('Content-Type', 'application/json');
callback(null, response);
}
};
:::Open the endpoint menu and set it to Public.
Environment Variables Setup
Set the following Environment Variables:
TWILIO_PHONE_NUMBER
: Twilio phone number for SMS sending
Operation Verification
Creating a Test Node.js Project
mkdir sms-limit-test
cd sms-limit-test
npm init -y
npm install axios dotenv
Setting Environment Variables
Create a .env
file with the following contents:
FUNCTION_ENDPOINT=https://your-service-1234.twil.io/send-sms
TO_NUMBER=+819012345678
```### Creating Test Scripts
Create a file named `test-sms-limit.js`.
:::details test-sms-limit.js
```javascript
// test-sms-limit.js
const axios = require('axios');
require('dotenv').config();
const functionUrl = process.env.FUNCTION_ENDPOINT;
async function testWithAxios() {
console.log('=== SMS Sending Limit Test ===\n');
const testParams = {
to: process.env.TO_NUMBER,
body: 'This is a test message'
};
console.log('Send parameters:', testParams);
console.log('Function URL:', functionUrl);
try {
console.log('First send test...');
const response1 = await axios.post(functionUrl, testParams, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
console.log('✅ First send result:');
console.log('Status:', response1.status);
console.log('Data:', typeof response1.data === 'string' ? JSON.parse(response1.data) : response1.data);
console.log('');
// Wait a bit
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('Second send test...');
const response2 = await axios.post(functionUrl, testParams, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
console.log('Second send result:');
console.log('Status:', response2.status);
console.log('Data:', typeof response2.data === 'string' ? JSON.parse(response2.data) : response2.data);
} catch (error) {
if (error.response) {
console.log('HTTP error:');
console.log('Status:', error.response.status);
let errorData = error.response.data;
if (typeof errorData === 'string') {
try {
errorData = JSON.parse(errorData);
} catch (e) {
// Keep as is if parsing fails
}
}
console.log('Data:', errorData);
if (error.response.status === 429) {
console.log('✅ Rate limit error correctly triggered');
}
} else {
console.error('❌ Unexpected error:', error.message);
}
}
console.log('\n=== Test completed ===');
}
if (require.main === module) {
testWithAxios().catch(console.error);
}
:::### Test Execution
node test-sms-limit.js
When successful, you will get output like the following:
=== SMS Sending Limitation Test ===
Sending parameters: { to: '+819012345678', body: 'テストメッセージです' }
Function URL: https://sms-limit-1234.twil.io/send-sms
1st sending test...
✅ 1st sending result:
Status: 200
Data: {
success: true,
messageSid: 'SM********',
currentCount: 1,
remainingCount: 0
}
2nd sending test...
HTTP error:
Status: 429
Data: {
success: false,
error: 'Daily SMS limit exceeded',
currentCount: 1,
limit: 1,
resetTime: '2025-07-30T23:59:59Z'
}
✅ Limit error occurred properly
=== Test Completed ===
Summary and Future Prospects
In this article, we built a system that limits the number of SMS messages sent per day by combining Twilio Functions and Momento Cache. By using date-based keys and TTL settings, we were able to implement a limitation feature that operates entirely within a serverless environment without requiring external infrastructure. This configuration allows for quick implementation of a cost-effective and efficient SMS sending limitation feature.
Future Prospects
- Feature Expansion: Adding user-specific limitations, time-specific limitations, and whitelist functionality
- Enhanced Monitoring: Building notification features for when limits are reached and usage analysis dashboards