# 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<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();
}
```

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<FunctionController> logger)
            : base(context, configuration, logger) { }

        public override async Task<IActionResult> 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> { ConversationChannel.Sms },
    ChannelProperties = new Dictionary<string, string>
    {
        ["SMS_SENDER"] = Configuration["FROM_NUMBER"] ?? ""
    }
});
```

## Keyword-based routing

Node.js
```typescript
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),
  });
}
```

C#
```csharp
public override async Task<IActionResult> 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<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}.`),
    });
  }
}
```

C#
```csharp
public override async Task<IActionResult> 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](/docs/functions/functions/templates) and [Configuration & Secrets](/docs/functions/functions/concepts/configuration-secrets).