https://a.storyblok.com/f/270183/1368x665/5fd62200f0/26apr_dev-blog_google-cloud-run_sms.jpg

How to Send and Receive SMS Messages with Node.js and Google Cloud Run

Published on April 21, 2026

Time to read: 9 minutes

Back in 2019, I organised a series of events featuring a “Cloud Run Club” at Finsbury Square Garden and  Victoria in London, followed by a Google Cloud Run workshop. Today, there will be no running, but you'll learn how to build and deploy an SMS application using Node.js, Express, and the Vonage Messages API on Google Cloud Run.

Unfortunately, while running, I can't scale to zero, but Google Cloud Run lets you deploy containerized applications that scale automatically. You only pay for what you use. It's great for building webhooks and APIs since you don't need to manage servers. Your app scales to zero when there's no traffic.

Outline and Prerequisites

  • Node.js v18 or later installed

  • Google Cloud CLI installed and configured

  • A Google Cloud account with billing enabled

  • A basic understanding of JavaScript and Node.js.

  • The complete code on GitHub

  • Vonage API Account

    To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

    This tutorial also uses a virtual phone number. To purchase one, go to Numbers > Buy Numbers and search for one that meets your needs.

Project Structure

You can find the complete code on GitHub. The project tree looks as follows.

blog-messages_api-cloud_run-sms/

├── index.js, contains our JavaScript

├── package.json, for our dependencies

├── .env.example, you can copy and create a .env from it to add your environment variables

├── .gitignore, for any files or folders not to be committed

└── private.key

Create a Vonage Application

I'll show you how to create a new Vonage application from the dashboard in the steps below. Alternatively, you can use the CLI to create an application.

  • To create an application, go to the Create an Application page on the Vonage Dashboard, and define a Name for your Application.

  • If you intend to use an API that uses Webhooks, you will need a private key. Click “Generate public and private key”, your download should start automatically. Store it securely; this key cannot be re-downloaded if lost. It will follow the naming convention private_<your app id>.key. This key can now be used to authenticate API calls. Note: Your key will not work until your application is saved.

  • Choose the capabilities you need (e.g., Voice, Messages, RTC, etc.) and provide the required webhooks (e.g., event URLs, answer URLs, or inbound message URLs). These will be described in the tutorial.

  • To save and deploy, click "Generate new application" to finalize the setup. Your application is now ready to use with Vonage APIs.

Messages API Capability and Configure Webhooks

Toggle the Messages option under capabilities. We will use the Messages API v1.

Set placeholder URLs for now. We'll update these after deployment:

  • Inbound URL: https://example.com/webhooks/inbound

  • Status URL: https://example.com/webhooks/status

Save your application and note down your Application ID.

Settings page showing message webhooks setup with fields for Inbound URL, Status URL, version selection, and enhanced security toggle.Messages Capability

To buy a virtual phone number, go to your API dashboard and follow the steps shown below.

Steps on how to purchase a phone number from the dashboard, from selecting the number and confirming the selection.Purchase a phone number

  1. Go to your API dashboard

  2. Navigate to BUILD & MANAGE > Numbers > Buy Numbers.

  3. Choose the attributes needed and then click Search

  4. Click the Buy button next to the number you want and validate your purchase

  5. To confirm you have purchased the virtual number, go to the left-hand navigation menu, under BUILD & MANAGE, click Numbers, then Your Numbers

Once we've selected our number, it’s time to link it to our Vonage application. Go to Your Numbers and click the pencil icon next to your number. Select your application under "Messages". At [00:01:19], you can see how to purchase and link a virtual number.

Set Messages API as Default

Go to Settings and scroll down to "SMS Settings". Make sure "Messages API" is selected as the default API for SMS, then click "Save changes".

Screenshot of SMS settings with a choice between SMS API and Messages API. Text explains usage affects webhook format.SMS SettingsVonage has two APIs for sending SMS: the SMS API and the Messages API. This tutorial uses the Messages API. If you don't switch this setting, your webhooks will receive a different payload format, and the code won't work correctly.

Initialize the Project

Create a directory for your application and change into it. Run npm init -y to initialize your project.

mkdir blog-messages_api-cloud_run-sms

cd blog-messages_api-cloud_run-sms

npm init -y

Install Dependencies

We are going to use the following dependencies in our project: express, @vonage/server-sdk, @vonage/jwt. You can install them by running the following command from your terminal or command prompt.

npm install express @vonage/server-sdk @vonage/jwt

Server Setup

Create the index.js file and initialize your server with the required packages. Configure a Vonage client using environment variables that are read from the .env file to be created from the .env.example file.

const express = require('express');

const path = require('path');

const { Vonage } = require('@vonage/server-sdk');

const { verifySignature } = require('@vonage/jwt');

const app = express();

app.use(express.json());

app.use(express.urlencoded({ extended: true }));

// Initialize Vonage client

const vonage = new Vonage({

  applicationId: process.env.VONAGE_APPLICATION_ID,

  privateKey: path.join(__dirname, 'private.key')

});

const FROM_NUMBER = process.env.VONAGE_NUMBER;

We use path.join(__dirname, 'private.key') to get the absolute path to the key file, so that it works both locally and when deployed to Cloud Run.

Add the Home Page

Add the form content and styling that the user must complete to send SMS messages. The user has to enter their phone number and a message, then click the submit button to send the SMS.

app.get('/', (req, res) => {

  res.send(`

    <!DOCTYPE html>

    <html>

    <head>

      <title>Vonage SMS on Cloud Run</title>

      <style>

        body { font-family: Arial, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }

        h1 { color: #7b00ff; }

        form { display: flex; flex-direction: column; gap: 15px; }

        input, textarea { padding: 10px; font-size: 16px; }

        button { padding: 12px; background: #7b00ff; color: white; border: none; cursor: pointer; }

      </style>

    </head>

    <body>

      <h1> Send SMS with Vonage </h1>

      <form action="/send" method="POST">

        <label>Phone Number (e.g. 444155551234)</label>

        <input type="tel" name="to" required>

        <label>Message</label>

        <textarea name="text" rows="4" required></textarea>

        <button type="submit">Send SMS</button>

      </form>

    </body>

    </html>

  `);

});

Add the Send Endpoint

Add the endpoint that sends the SMS using the Vonage Messages API. The vonage.messages.send() method accepts an object with channel set to 'sms' and message_type set to 'text'. The response includes a message_uuid you can use to track the message.

app.post('/send', async (req, res) => {

  const { to, text } = req.body;

 try {

    const response = await vonage.messages.send({

      message_type: 'text',

      to: to,

      from: FROM_NUMBER,

      channel: 'sms',

      text: text

    });

    console.log('SMS sent:', response.message_uuid);

    res.sendSMS sent! Message ID: ${response.message_uuid});

  } catch (error) {

    console.error('Error sending SMS:', error);

    res.status(500).sendFailed to send SMS: ${error.message});

  }

});

Add Webhook Endpoints

When someone sends an SMS to your Vonage virtual number you previously purchased, the SMS message arrives at Vonage, Vonage sends a POST request to your Google Cloud Run /webhooks/inbound endpoint, and finally, your app processes the message and responds with 200 OK, if everything works well. Add the webhook endpoints that use this verification, as shown below. The status webhook (/webhooks/status) receives delivery receipts. You'll know whether your message was delivered or failed.

app.post('/webhooks/inbound', (req, res) => {

  verifyJWT(req);

  console.log('Inbound SMS received:');

  console.log('From:', req.body.from);

  console.log('Text:', req.body.text);

  

  res.status(200).send('OK');

});

app.post('/webhooks/status', (req, res) => {

  verifyJWT(req);

  console.log('Status update:', req.body.status);

  res.status(200).send('OK');

});

Add a JWT verification function to verify if the incoming request (e.g., message or call) came from Vonage. 

The VONAGE_API_SIGNATURE_SECRET variable is the secret used to sign the request corresponding to the signature secret associated with the API key included in the JWT claims. You can identify your signature secret on the Dashboard settings

You can learn more about verifying the request.

const verifyJWT = (req) => {
  // Verify if the incoming request came from Vonage
  const jwtToken = req.headers.authorization.split(" ")[1];
  if(!verifySignature(jwtToken, process.env.VONAGE_API_SIGNATURE_SECRET)) {
    console.error("Signature does not match");
    throw new Error("Not a Vonage API request");
  }

  console.log("JWT verified");
}

Make sure to install the @vonage/jwt dependency.

npm install @vonage/jwt

Start the Server

Cloud Run sets the PORT environment variable automatically, so we use that if available. Add the code to start the server:

const PORT = process.env.PORT || 8080;

app.listen(PORT, () => {

  console.logServer running on port ${PORT});

});

Limited Local Testing

You can run the app locally to check that it starts without errors, but you won't be able to send SMS messages yet. The Vonage Messages API needs to reach your webhook URLs to verify requests and send status updates. That's why we're deploying to Cloud Run first.

If you want to verify the app runs, run the JavaScript file.

node index.js

Open http://localhost:3000 to see the form. The app should start without errors, but sending messages will fail until we deploy and configure the webhooks.

Deploy to Google Cloud Run

Now that the app works locally, let's deploy it to Google Cloud Run, so it's accessible on the internet and can receive webhooks from Vonage. 

Set Up Google Cloud

If you don't have a Google Cloud account yet, create one as required in the prerequisites.

Install the Google Cloud CLI

Download and install the Google Cloud CLI for your operating system. In the next subsection, I’ll show how to do it on macOS, but you can refer to the link above for other OSes. I wanted to show the steps I’ve done myself because I ran into some errors, and share some tips that might help.

On macOS

# Download the archive (Apple Silicon)

curl -O https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-darwin-arm.tar.gz

# Extract it

tar -xzf google-cloud-cli-darwin-arm.tar.gz

# Run the install script

./google-cloud-sdk/install.sh

In case there’s a Python version error: the gcloud CLI requires Python 3.10 or higher. If you see a TypeError about unsupported operand type(s) for |, your Python is too old. Install a newer version with Homebrew as shown below (or any other way you prefer) and point gcloud to it:

brew install python@3.12

export CLOUDSDK_PYTHON=$(brew --prefix python@3.12)/bin/python3.12

./google-cloud-sdk/install.sh

The install script will ask if you want to add gcloud to your PATH. Choose yes. Then restart your command prompt/ terminal or run:

source ~/.zshrc

Now initialize gcloud:

gcloud init

This will open a browser window to log in to your Google account and select or create a new Google Cloud project.

Create a Google Cloud Project

If you don't have a project yet, create one. The Project IDs must be globally unique, so you can add some random characters.

gcloud projects create vonage-sms-yourname --name="Vonage Messages API Demo"

gcloud config set project vonage-sms-yourname

You can also create a project from the Google Cloud Console.

Enable a Google Cloud Billing Account

Cloud Run requires billing to be enabled. Go to the Billing page and link a billing account to your project. 

Enable Required Google Cloud APIs

Enable the Cloud Build and Cloud Run APIs:

gcloud services enable cloudbuild.googleapis.com run.googleapis.com

Deploy to Google Cloud Run

For production applications, consider using Google Secret Manager to store sensitive values. For this tutorial, we'll pass environment variables directly.

Make sure your “private.key” file is in the project directory. It gets bundled with your source code during deployment.

Deploy:

gcloud run deploy vonage-sms \

  --source . \

  --region us-central1 \

  --allow-unauthenticated \

  --set-env-vars "VONAGE_APPLICATION_ID=your_app_id" \

  --set-env-vars "VONAGE_NUMBER=444155551234" \

  --set-env-vars "VONAGE_API_SIGNATURE_SECRET=your_signature_secret"

First deploy: you'll see a prompt about creating an Artifact Registry repository named cloud-run-source-deploy to store your container images. Type y to continue.

The first deployment takes a couple of minutes. When it's done, you'll see a URL like https://vonage-sms-abc123-uc.a.run.app.

Update Vonage Webhooks

Now that you have your Cloud Run URL, go back to your Vonage Application and update the webhook URLs:

  • Inbound URL: https://your-cloud-run-url/webhooks/inbound

  • Status URL: https://your-cloud-run-url/webhooks/status

Don't forget to click Save changes at the bottom right of the page.

Test It Out

It’s time to test everything we’ve been setting up and see things running!

Send an SMS

Open your Cloud Run URL in a browser (the URL you got after deployment, like https://vonage-sms-abc123-uc.a.run.app). You should see the SMS form we built. Enter your phone number and a message, then press send. If everything is configured correctly, you'll receive the SMS on your phone within a few seconds. You can check country-specific features and restrictions.

Receive an SMS

Now test the other direction. Send an SMS from your phone to the Vonage number you purchased and linked to the application you created. This triggers the inbound webhook we set up. You can check the logs in two places:

From the Command Line

gcloud run logs read vonage-sms --region us-central1

From the Vonage Dashboard

Go to your Vonage Application, click on your application, and check the logs section to see message activity and any webhook errors. You should see your inbound message logged with the sender's number and text.

Conclusion

You've built and deployed an SMS application on Google Cloud Run using the Vonage Messages API. The app can send messages through a web form and receive incoming messages through webhooks. 

Have a question or want to share what you're building?

Stay connected and keep up with the latest developer news, tips, and events.

Further Reading

Share:

https://a.storyblok.com/f/270183/400x400/3f6b0c045f/amanda-cavallaro.png
Amanda CavallaroDeveloper Advocate