Skip to content

Build an SMS Responder

Receive inbound SMS messages and send replies using the Sinch Conversation API. The same code works for WhatsApp, Messenger, and any other channel your Conversation app supports.

Prerequisites

Declare secrets in .env with empty values and store them with sinch secrets add:

sinch secrets add CONVERSATION_APP_ID your-app-id
sinch secrets add PROJECT_ID your-project-id
sinch secrets add PROJECT_ID_API_KEY your-key-id
sinch secrets add PROJECT_ID_API_SECRET your-key-secret

Basic responder

import type { FunctionContext } from '@sinch/functions-runtime';
import {
  ConversationController,
  getText,
  getChannel,
  getContactId,
} from '@sinch/functions-runtime';
import type { MessageInboundEvent } from '@sinch/functions-runtime';

class SmsHandler extends ConversationController {
  async handleMessageInbound(event: MessageInboundEvent): Promise<void> {
    const text = getText(event);
    if (!text) return;

    const channel = getChannel(event);
    const contactId = getContactId(event);
    console.log(`Inbound from ${channel}: "${text}" (contact: ${contactId})`);

    await this.conversation!.messages.send({
      sendMessageRequestBody: this.reply(event, `You said: ${text}`),
    });
  }
}

export async function setup(context: FunctionContext) {
  const handler = new SmsHandler(context.conversation, {
    CONVERSATION_APP_ID: context.env.CONVERSATION_APP_ID,
    FROM_NUMBER: context.env.FROM_NUMBER,
  });
  return handler.getWebhookHandler();
}

Helper functions

import {
  getText,
  getChannel,
  getContactId,
  getConversationId,
  getIdentity,
  getMedia,
  getPostbackData,
  isTextMessage,
  isMediaMessage,
  isPostback,
} from '@sinch/functions-runtime';

const text = getText(event); // 'Hello'
const channel = getChannel(event); // 'SMS', 'WHATSAPP', 'MESSENGER'
const contactId = getContactId(event); // Sinch contact ID
const identity = getIdentity(event); // Sender's phone/PSID/etc.

Sending replies

Simple text reply

await this.conversation!.messages.send({
  sendMessageRequestBody: this.reply(event, 'Thanks for your message!'),
});

Send to a specific contact by ID

await this.conversation!.messages.send({
  sendMessageRequestBody: {
    app_id: this.config.CONVERSATION_APP_ID,
    recipient: { contact_id: contactId },
    message: { text_message: { text: 'A proactive message' } },
  },
});

Send via explicit channel (SMS)

For SMS you must set SMS_SENDER with your Sinch number. this.reply() / Reply() does this automatically using FROM_NUMBER. For manual sends:

await this.conversation!.messages.send({
  sendMessageRequestBody: {
    app_id: this.config.CONVERSATION_APP_ID,
    recipient: { contact_id: contactId },
    message: { text_message: { text: 'Reply via SMS' } },
    channel_priority_order: ['SMS'],
    channel_properties: {
      SMS_SENDER: this.config.FROM_NUMBER,
    },
  },
});

Keyword-based routing

async handleMessageInbound(event: MessageInboundEvent): Promise<void> {
  const text = getText(event)?.trim().toUpperCase();
  if (!text) return;

  let reply: string;
  if (text === 'HELP') {
    reply = 'Text HOURS for our hours, or STOP to unsubscribe.';
  } else if (text === 'HOURS') {
    reply = 'We are open Monday through Friday, 9 AM to 5 PM EST.';
  } else if (text === 'STOP') {
    reply = 'You have been unsubscribed. Text START to re-subscribe.';
  } else {
    reply = 'Thanks for reaching out. Text HELP for available commands.';
  }

  await this.conversation!.messages.send({
    sendMessageRequestBody: this.reply(event, reply),
  });
}

Multi-channel handling

Use getChannel() to branch per channel:

async handleMessageInbound(event: MessageInboundEvent): Promise<void> {
  const channel = getChannel(event);
  const text = getText(event);

  if (channel === 'WHATSAPP') {
    await this.conversation!.messages.send({
      sendMessageRequestBody: this.reply(event, `WhatsApp: ${text}`),
    });
  } else if (channel === 'SMS') {
    const shortReply = (text ?? '').substring(0, 100);
    await this.conversation!.messages.send({
      sendMessageRequestBody: this.reply(event, shortReply),
    });
  } else {
    await this.conversation!.messages.send({
      sendMessageRequestBody: this.reply(event, `Got your message on ${channel}.`),
    });
  }
}

Webhook endpoint

The conversation webhook is at POST /webhook/conversation (C#) or via handler.getWebhookHandler() (Node.js). Configure this URL in the Sinch Dashboard under your Conversation app's webhook settings.

For sinch.json configuration and environment variables, see Templates and Configuration & Secrets.