Implementing daily SMS sending API call limits with Twilio Functions + Momento

Implementing daily SMS sending API call limits with Twilio Functions + Momento

I'll introduce a low-cost method of limiting daily SMS sending frequency by combining Twilio Functions and Momento Cache. This achieves a limitation feature that is completed within a serverless environment without requiring complex infrastructure.
2025.07.30

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

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

Twilio Console

Momento Account Setup

Create an account on the Momento Console and perform the following steps:

  1. Create a cache

    • Cache name: Any name (e.g., sms-limit)
      Create cache
  2. Create API key

    • Create a Fine-grained Access Key
    • Grant readwrite permission to the created cache
      Create API key

Implementation Steps### Creating Twilio Functions

  1. Creating a New Service

    • Create a new Service from Functions and Assets > Services in the Twilio Console
  2. Adding Dependencies

    • Add @gomomento/sdk to Dependencies
      Dependencies
  3. 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
      Add Asset
    • Set the filename to momento-token
    • Set Visibility to Private
      Visibility
    • Enter the Momento API key you created earlier
      momento-token content
    • Execute Deploy All to complete the deployment### Implementation of SMS Sending Limit Function

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.

Endpoint access restriction settings

Environment Variables Setup

Set the following Environment Variables:

  • TWILIO_PHONE_NUMBER: Twilio phone number for SMS sending

Environment variables

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

Share this article

FacebookHatena blogX

Related articles