> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.fortresstech.io/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.fortresstech.io/_mcp/server.

# Send Message

POST https://api.fortresstech.io/v1/messages
Content-Type: application/json

Available by RequestSend a message to a customer or prospect. Supply exactly one of `customerId` or `prospectId`.
Supplying both is not rejected: `customerId` takes precedence and the `prospectId` you sent is
ignored, so the message is attributed to the customer.Fortress resolves the recipient's contact details and property phone number, appends the message to the
existing conversation with that recipient (creating one if none exists), and hands it to the carrier.A `201` means the message was accepted, not that it was delivered. The returned `messageStatus` will be
`PROCESSING` while delivery is in flight; poll `GET /messages/{messageId}` until it reaches a terminal
status of `DELIVERED`, `UNDELIVERED`, or `FAILED`.`idempotencyId` is required. Reusing a key returns the message created by the original request instead
of sending a duplicate, so a retry after a network timeout is always safe.This endpoint is part of our evolving API ecosystem.
To explore availability for your integration, please contact our team.

Reference: https://developer.fortresstech.io/api-reference/messaging/send-message

## Authentication

- `x-api-key` header (required) — API Key authentication via header

## Request

### Body (application/json)

This endpoint expects a MessageCreate.

- `content` (string, required) — The body of the message.
- `idempotencyId` (string, required) — Uniquely identifies this send request. Reusing a key returns the original message rather than sending a duplicate.
- `customerId` (string, optional) — The customer to send to. Supply exactly one of `customerId` or `prospectId`. If both are supplied, `customerId` takes precedence and `prospectId` is ignored.
- `prospectId` (string, optional) — The prospect to send to. Supply exactly one of `customerId` or `prospectId`. If both are supplied, `customerId` takes precedence and `prospectId` is ignored.
- `metadata` (map from string to any, optional) — Arbitrary key/value data to store alongside the message. Returned unchanged on reads.

## Response

### 201

The message was accepted for delivery.

- `id` (string, optional) — Unique identifier for the message.
- `conversationId` (string, optional) — The conversation this message belongs to. Messages to the same recipient from the same property are grouped into a single conversation.
- `type` (enum, optional) — Direction and origin of the message. Messages created through `POST /messages` are always `OUTBOUND`.
  - Allowed values: `OUTBOUND`, `INBOUND`, `INTERNAL`, `EMAIL`, `UNRECOGNIZED_KEYWORD_RESPONSE`
- `content` (string, optional) — The body of the message. Limited to 1600 characters on send.
- `messageStatus` (string, optional) — Current delivery status. Statuses reported by the downstream carrier or email provider are passed through verbatim, so this is an open set rather than a fixed enum. Statuses set by Fortress: `CREATED`, `PROCESSING`, `FAILED`, `INVALID_PHONE_NUMBER`, `INVALID_EMAIL`, `RECEIVED`, `AUTOMATED`, `MASS_COMMUNICATION`. Statuses reported by the carrier: `QUEUED`, `SENT`, `DELIVERED`, `UNDELIVERED`, `FAILED`. The terminal statuses for SMS are `DELIVERED`, `UNDELIVERED`, and `FAILED`. For email they are `DELIVERED`, `FAILED`, and `REJECTED`.
- `messageStatusReason` (string, optional, nullable) — Explanation for the current status. Populated when a message fails to send.
- `messageSid` (string, optional) — Identifier assigned by the downstream SMS carrier. Absent until the message has been accepted for delivery.
- `customerStatus` (string, optional) — Status of the recipient at the time the message was created, as recorded in Fortress.
- `idempotencyId` (string, optional) — Caller-supplied key that uniquely identifies the send request. Reusing a key returns the original message rather than sending a duplicate.
- `isAutomated` (boolean, optional) — True when the message was generated by a Fortress automation rather than a person.
- `needsHelp` (boolean, optional, nullable) — True while this message has an open needs-help flag. Absent on messages that have never been flagged.
- `needsHelpDetails` (MessageNeedsHelpDetails, optional, nullable) — Who raised the needs-help flag, why, and who resolved it. Absent on messages that have never been flagged. The flag details survive resolution, so this still describes the original request after `needsHelp` returns to false.
- `subject` (string, optional) — Subject line. Email messages only.
- `emailAddress` (string, optional) — Recipient email address. Email messages only.
- `attachments` (list of MessageAttachmentsItems, optional) — Media attached to the message.
- `metadata` (map from string to any, optional) — Caller-supplied metadata, plus `sentAt` and `sentBy` recorded by Fortress at send time.
- `organizationId` (string, optional)
- `customerId` (string, optional) — Set when the recipient is a customer.
- `prospectId` (string, optional) — Set when the recipient is a prospect.
- `userId` (string, optional) — The Fortress user the message was sent on behalf of.
- `createdAt` (datetime, optional)
- `updatedAt` (datetime, optional)

## Errors

### 400 Bad Request Error

Neither `customerId` nor `prospectId` was supplied, `idempotencyId` is missing, or `content` is empty or longer than 1600 characters.

- `type` (string, optional)
- `title` (string, optional)
- `status` (string, optional)
- `detail` (string, optional)
- `instance` (string, optional)

### 404 Not Found Error

The customer or prospect could not be found.

- `type` (string, optional)
- `title` (string, optional)
- `status` (string, optional)
- `detail` (string, optional)
- `instance` (string, optional)

### 422 Unprocessable Entity Error

The recipient has no reachable phone number, so the message could not be sent.

- `type` (string, optional)
- `title` (string, optional)
- `status` (string, optional)
- `detail` (string, optional)
- `instance` (string, optional)

## Types

### MessageNeedsHelpDetails

Who raised the needs-help flag, why, and who resolved it. Absent on messages that have never been flagged. The flag details survive resolution, so this still describes the original request after `needsHelp` returns to false.

- `reason` (string, optional, nullable) — Free-text reason supplied when the flag was raised.
- `flaggedByType` (enum, optional, nullable) — Who raised the flag: `agent` for Leslie, `user` for a Fortress staff member.
  - Allowed values: `agent`, `user`
- `flaggedById` (string, optional, nullable) — Identifies who raised the flag. `leslie` for the agent, otherwise a Fortress user id.
- `flaggedAt` (datetime, optional, nullable) — When the flag was raised.
- `resolvedByType` (enum, optional, nullable) — Who resolved the flag, using the same `agent`/`user` distinction as the flag itself.
  - Allowed values: `agent`, `user`
- `resolvedById` (string, optional, nullable) — Identifies who resolved the flag.
- `resolvedAt` (datetime, optional, nullable) — When the flag was resolved. Null while the flag is still open.

### MessageAttachmentsItems

- `key` (string, optional)
- `bucket` (string, optional)
- `type` (string, optional) — MIME type of the attachment.
- `url` (string, optional) — Time-limited download URL, generated per request. Expires 15 minutes after the response is issued.

## Examples

**Request**

```json
{
  "content": "string",
  "idempotencyId": "string"
}
```

**Response**

```json
{
  "id": "string",
  "conversationId": "string",
  "type": "OUTBOUND",
  "content": "string",
  "messageStatus": "string",
  "messageStatusReason": "string",
  "messageSid": "string",
  "customerStatus": "string",
  "idempotencyId": "string",
  "isAutomated": true,
  "needsHelp": true,
  "needsHelpDetails": {
    "reason": "string",
    "flaggedByType": "agent",
    "flaggedById": "string",
    "flaggedAt": "2024-01-15T09:30:00Z",
    "resolvedByType": "agent",
    "resolvedById": "string",
    "resolvedAt": "2024-01-15T09:30:00Z"
  },
  "subject": "string",
  "emailAddress": "string",
  "attachments": [
    {
      "key": "string",
      "bucket": "string",
      "type": "string",
      "url": "string"
    }
  ],
  "metadata": {},
  "organizationId": "string",
  "customerId": "string",
  "prospectId": "string",
  "userId": "string",
  "createdAt": "2024-01-15T09:30:00Z",
  "updatedAt": "2024-01-15T09:30:00Z"
}
```

**SDK Code**

```python
import requests

url = "https://api.fortresstech.io/v1/messages"

payload = {
    "content": "string",
    "idempotencyId": "string"
}
headers = {
    "x-api-key": "<apiKey>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript
const url = 'https://api.fortresstech.io/v1/messages';
const options = {
  method: 'POST',
  headers: {'x-api-key': '<apiKey>', 'Content-Type': 'application/json'},
  body: '{"content":"string","idempotencyId":"string"}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.fortresstech.io/v1/messages"

	payload := strings.NewReader("{\n  \"content\": \"string\",\n  \"idempotencyId\": \"string\"\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("x-api-key", "<apiKey>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby
require 'uri'
require 'net/http'

url = URI("https://api.fortresstech.io/v1/messages")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<apiKey>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"content\": \"string\",\n  \"idempotencyId\": \"string\"\n}"

response = http.request(request)
puts response.read_body
```

```java
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.fortresstech.io/v1/messages")
  .header("x-api-key", "<apiKey>")
  .header("Content-Type", "application/json")
  .body("{\n  \"content\": \"string\",\n  \"idempotencyId\": \"string\"\n}")
  .asString();
```

```php
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.fortresstech.io/v1/messages', [
  'body' => '{
  "content": "string",
  "idempotencyId": "string"
}',
  'headers' => [
    'Content-Type' => 'application/json',
    'x-api-key' => '<apiKey>',
  ],
]);

echo $response->getBody();
```

```csharp
using RestSharp;

var client = new RestClient("https://api.fortresstech.io/v1/messages");
var request = new RestRequest(Method.POST);
request.AddHeader("x-api-key", "<apiKey>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"content\": \"string\",\n  \"idempotencyId\": \"string\"\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift
import Foundation

let headers = [
  "x-api-key": "<apiKey>",
  "Content-Type": "application/json"
]
let parameters = [
  "content": "string",
  "idempotencyId": "string"
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.fortresstech.io/v1/messages")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```