---
title: Send a Message Programmatically
slug: send-a-message-programmatically
docTags: 
createdAt: 2026-09-14T23:41:45.523Z
---

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

```swift
import Kindly
import Combine

var cancellables = Set<AnyCancellable>()

KindlySDK.sendMessage("Hi, I have a question about my order")
    .sink(
        receiveCompletion: { completion in
            if case .failure(let error) = completion {
                print("Send failed: \(error)")
            }
        },
        receiveValue: { (message: ExternalChatMessage) in
            print("Sent — server id: \(message.id)")
        }
    )
    .store(in: &cancellables)
```

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

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

## With per-call context

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

```swift
KindlySDK.sendMessage(
    "Where is my order?",
    newContext: ["orderId": "12345", "tier": "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:

```swift
KindlySDK.events
    .sink { event in
        if case .message(let newMessage, _) = event.detail {
            print("\(newMessage.sender) said: \(newMessage.text ?? "")")
        }
    }
    .store(in: &cancellables)
```

## Errors

The promise rejects with a `KindlySDKError` in these cases:

| Error                | Cause                                                                                                                                   |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `.sdkNotInitialized` | `KindlySDK.start(...)` was never called.                                                                                                |
| `.chatNotConnected`  | The chat is not currently connected. Observe `KindlySDK.state` for `isConnected == true` before calling, or call `displayChat()` first. |
| `.invalidData`       | The message text is empty or only whitespace.                                                                                           |
| Other                | Network or server errors are forwarded as-is.                                                                                           |

## Behaviour summary

- Trims whitespace; empty input rejects with `.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.
