---
title: Send a Message Programmatically
slug: sdk/android-sdk-send-message
createdAt: 2026-11-04T11:40:08.868Z
---

Drive the chat from the host app: send a message **as the user** without going through the input field. Useful for guided flows, in-app actions ("ask Kindly about my order"), or composing a message from contextual UI.

## Usage

```kotlin
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import no.kindly.chatsdk.chat.KindlySDK

lifecycleScope.launch {
    try {
        val message = KindlySDK.sendMessage("Hi, I have a question about my order").await()
        println("Sent — server id: ${message.id}")
    } catch (e: Exception) {
        println("Send failed: ${e.message}")
    }
}
```

If you don't need the reconciled message back, just call it and ignore the `Deferred`:

```kotlin
KindlySDK.sendMessage("Hi, I have a question about my order")
```

You can also observe completion without a coroutine via `invokeOnCompletion`:

```kotlin
KindlySDK.sendMessage("Hi").invokeOnCompletion { error ->
    if (error != null) println("Send failed: $error")
}
```

## With per-call context

Attach key/value context to this message only — overrides any context previously staged via `setNewContext(...)`:

```kotlin
KindlySDK.sendMessage(
    message = "Where is my order?",
    newContext = mapOf("orderId" to "12345", "tier" to "premium"),
)
```

## Important: do not also display the message yourself

The SDK renders the bubble locally with a temporary id the moment you call `sendMessage`, then reconciles with the server-assigned id when the POST response returns. If you also add the message to a chat log you maintain, you'll see it twice.

The message also fires through the unified event stream:

```kotlin
import no.kindly.chatsdk.chat.models.KindlyEvent

lifecycleScope.launch {
    KindlySDK.events.collect { event ->
        if (event.detail is KindlyEvent.Detail.Message) {
            val msg = (event.detail as KindlyEvent.Detail.Message).newMessage
            println("${msg.sender}: ${msg.text}")
        }
    }
}
```

## Errors

The `Deferred` completes exceptionally with a `KindlySDKError` in these cases:

| Error                              | Cause                                                                                                                                         |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `KindlySDKError.SDKNotInitialized` | `KindlySDK.start(...)` was never called.                                                                                                      |
| `KindlySDKError.ChatNotConnected`  | The chat is not currently connected. Observe `KindlySDK.state` for `isConnected == true` before calling, or call `launchChat(context)` first. |
| `KindlySDKError.InvalidData`       | The message text is empty or only whitespace.                                                                                                 |
| `KindlySDKError.ApiError` / other  | Network or server errors are forwarded.                                                                                                       |

## Behaviour summary

- Trims whitespace; empty input completes exceptionally with `KindlySDKError.InvalidData`.
- Optimistic local render — bubble appears immediately with a temp id.
- POSTs to `/message`, reconciles temp id → server id on success.
- Emits `KindlyEvent.message` for the user's message (consumers of `KindlySDK.events` see it like any other message).
- Per-call `newContext` overrides global `setNewContext(...)` for this single call.
- Works whether or not the chat UI is currently displayed — the message is delivered, and when the user opens the chat next, they see it in place.
