How to Send OTP with Google Cloud Run (2026)
Cloud Run OTP tutorial using StartMessaging. Containerised Node service, Secret Manager for keys, Memorystore for rate limit, deployed via gcloud.
Cloud Run is a tidy target for containerised Indian SaaS. This tutorial wires StartMessaging via a small Node container.
Overview
- Containerise a Node app with /send + /verify routes.
- Pull SM_API_KEY from Secret Manager at startup.
- Per-phone hourly cap in Memorystore (Redis).
- Deploy with
gcloud run deploy.
Cloud Run Service
# Dockerfile
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENV PORT=8080
CMD ["node", "src/index.js"]
// src/index.ts
import express from 'express';
import { SecretManagerServiceClient } from '@google-cloud/secret-manager';
import { randomUUID, randomInt } from 'node:crypto';
const sm = new SecretManagerServiceClient();
let apiKey: string | null = null;
async function getKey() {
if (apiKey) return apiKey;
const [v] = await sm.accessSecretVersion({ name: process.env.SM_API_KEY_REF! });
apiKey = v.payload!.data!.toString();
return apiKey;
}
const app = express();
app.use(express.json());
app.post('/auth/send-otp', async (req, res) => {
const k = await getKey();
const r = await fetch('https://api.startmessaging.com/otp/send', {
method: 'POST',
headers: { 'X-API-Key': k, 'Content-Type': 'application/json' },
body: JSON.stringify({
phoneNumber: req.body.phoneNumber,
// The service generates the code; StartMessaging delivers it
variables: { otp: String(randomInt(100000, 1000000)) },
idempotencyKey: randomUUID(),
}),
});
const json = await r.json();
if (!r.ok) return res.status(r.status).json({ error: json.message ?? 'send failed' });
// data: { otpRequestId, messageId, status } — the client sends otpRequestId back as requestId
res.json({ requestId: json.data.otpRequestId });
});
app.post('/auth/verify-otp', async (req, res) => {
const k = await getKey();
const r = await fetch('https://api.startmessaging.com/otp/verify', {
method: 'POST',
headers: { 'X-API-Key': k, 'Content-Type': 'application/json' },
body: JSON.stringify({ requestId: req.body.requestId, otpCode: req.body.otpCode }),
});
res.status(r.status).send(await r.text());
});
app.listen(Number(process.env.PORT) || 8080);
Secret Manager
gcloud secrets create sm-api-key --replication-policy=automatic
echo -n 'sm_live_xxx' | gcloud secrets versions add sm-api-key --data-file=-
Memorystore Rate Limit
Spin a small Memorystore Redis; per-phone INCR with EXPIRE 3600.
Deploy with gcloud
gcloud run deploy otp-api --source . \\
--set-env-vars SM_API_KEY_REF=projects/<id>/secrets/sm-api-key/versions/latest \\
--service-account otp-runtime@<id>.iam.gserviceaccount.com
FAQ
Lambda equivalent in our AWS Lambda guide.
StartMessaging Team
StartMessaging Team