# Build an SMS Responder Receive inbound SMS messages and send replies using the [Sinch Conversation API](/docs/conversation/). 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`: ```bash 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 Node.js ```typescript 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 { 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(); } ``` C# ```csharp using Microsoft.AspNetCore.Mvc; using Sinch.Conversation.Hooks; using SinchFunctions.Utils; namespace SinchFunctions { public class FunctionController : SinchConversationController { public FunctionController( FunctionContext context, IConfiguration configuration, ILogger logger) : base(context, configuration, logger) { } public override async Task MessageInbound(MessageInboundEvent inbound) { var text = inbound.GetText(); if (string.IsNullOrEmpty(text)) return Ok(); var channel = inbound.GetChannel(); Logger.LogInformation("Inbound from {Channel}: {Text}", channel, text); await Context.Conversation!.Messages.Send(Reply(inbound, $"You said: {text}")); return Ok(); } } } ``` ## Helper functions Node.js ```typescript 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. ``` C# ```csharp using SinchFunctions.Utils; var text = inbound.GetText(); var channel = inbound.GetChannel(); // "SMS", "WHATSAPP", "MESSENGER" var contactId = inbound.GetContactId(); var identity = inbound.GetIdentity(); // Sender phone/PSID ``` ## Sending replies ### Simple text reply Node.js ```typescript await this.conversation!.messages.send({ sendMessageRequestBody: this.reply(event, 'Thanks for your message!'), }); ``` C# ```csharp await Context.Conversation!.Messages.Send(Reply(inbound, "Thanks for your message!")); ``` ### Send to a specific contact by ID Node.js ```typescript await this.conversation!.messages.send({ sendMessageRequestBody: { app_id: this.config.CONVERSATION_APP_ID, recipient: { contact_id: contactId }, message: { text_message: { text: 'A proactive message' } }, }, }); ``` C# ```csharp await Context.Conversation!.Messages.Send(new SendMessageRequest { AppId = Configuration["CONVERSATION_APP_ID"], Recipient = new Recipient { ContactId = contactId }, Message = new AppMessage { TextMessage = new TextMessage { 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: Node.js ```typescript 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, }, }, }); ``` C# ```csharp await Context.Conversation!.Messages.Send(new SendMessageRequest { AppId = Configuration["CONVERSATION_APP_ID"], Recipient = new Recipient { ContactId = contactId }, Message = new AppMessage { TextMessage = new TextMessage { Text = "Reply via SMS" } }, ChannelPriorityOrder = new List { ConversationChannel.Sms }, ChannelProperties = new Dictionary { ["SMS_SENDER"] = Configuration["FROM_NUMBER"] ?? "" } }); ``` ## Keyword-based routing Node.js ```typescript async handleMessageInbound(event: MessageInboundEvent): Promise { 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), }); } ``` C# ```csharp public override async Task MessageInbound(MessageInboundEvent inbound) { var text = inbound.GetText()?.Trim().ToUpperInvariant(); if (string.IsNullOrEmpty(text)) return Ok(); var reply = text switch { "HELP" => "Text HOURS for our hours, or STOP to unsubscribe.", "HOURS" => "We are open Monday through Friday, 9 AM to 5 PM EST.", "STOP" => "You have been unsubscribed. Text START to re-subscribe.", _ => "Thanks for reaching out. Text HELP for available commands." }; await Context.Conversation!.Messages.Send(Reply(inbound, reply)); return Ok(); } ``` ## Multi-channel handling Use `getChannel()` to branch per channel: Node.js ```typescript async handleMessageInbound(event: MessageInboundEvent): Promise { 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}.`), }); } } ``` C# ```csharp public override async Task MessageInbound(MessageInboundEvent inbound) { var channel = inbound.GetChannel(); var text = inbound.GetText() ?? ""; var reply = channel switch { "WHATSAPP" => $"WhatsApp: {text}", "SMS" => text.Length > 100 ? text[..100] : text, _ => $"Got your message on {channel}." }; await Context.Conversation!.Messages.Send(Reply(inbound, reply)); return Ok(); } ``` ## 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](#).