---
title: "Send an SMS or a letter"
description: "Preview, price, and send a paid follow-up without creating a duplicate."
canonical: https://developer.billabex.com/en/guides/outgoing-messages/
lang: en
alternate: https://developer.billabex.com/fr/guides/messages-sortants/
last-updated: 2026-09-12
---

# Send an SMS or a letter

> Preview, price, and send a paid follow-up without creating a duplicate.

Source: https://developer.billabex.com/en/guides/outgoing-messages/
Language: English (en)
French version: https://developer.billabex.com/fr/guides/messages-sortants/

The public API can send an SMS, a tracked letter, or a registered letter with acknowledgement of
receipt. These sends consume credits and cannot be recalled. The recommended flow is therefore:

1. preview the message and its cost;
2. set `maxCredits` to the cost you accept;
3. send with a stable `idempotencyKey`;
4. read the communication back to follow its delivery.

Every endpoint on this page uses the OAuth scope `dunning:manage`. Reading a communication also
accepts `communications:read`.

## Pricing

| Channel            | Cost                                                       |
| ------------------ | ---------------------------------------------------------- |
| `Sms`              | 1 credit per segment, following GSM-7 or Unicode encoding  |
| `TrackedLetter`    | 6 credits, plus 1 credit per page after the first          |
| `RegisteredLetter` | 12 credits, plus 1 credit per page after the first         |

The server always computes the cost from the final content. A long SMS can span several segments.
Invoices and credit notes attached to a letter increase its page count.

## Preview

```http
POST https://next.billabex.com/api/public/v1/accounts/ACCOUNT_ID/outgoing-message-communications/preview
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
```

SMS example:

```json
{
  "channel": "Sms",
  "contactId": "CONTACT_ID",
  "recipientPhone": "+33612345678",
  "invoiceIds": ["INVOICE_ID"],
  "paymentDeadline": "2026-08-25"
}
```

Letter example:

```json
{
  "channel": "RegisteredLetter",
  "invoiceIds": ["INVOICE_ID"],
  "creditNoteIds": [],
  "paymentDeadline": "2026-08-25",
  "recipientAddress": {
    "fullName": "Client SA",
    "street": "1 rue de la Paix",
    "postalCode": "75001",
    "city": "Paris",
    "country": "FR"
  },
  "includePdf": true
}
```

The response charges no credit:

```json
{
  "pdfBase64": null,
  "html": "",
  "body": "Your final message",
  "pageCount": 0,
  "credits": 1
}
```

For an SMS, `pageCount` is `0`, `html` is empty and `pdfBase64` is `null`. For a letter,
`pageCount` includes the attachments. `includePdf: true` assembles the complete document set into
`pdfBase64`.

## Send without creating a duplicate

```http
POST https://next.billabex.com/api/public/v1/accounts/ACCOUNT_ID/outgoing-message-communications
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json
```

Reuse the fields from the preview and add the two safeguards:

```json
{
  "channel": "Sms",
  "contactId": "CONTACT_ID",
  "recipientPhone": "+33612345678",
  "invoiceIds": ["INVOICE_ID"],
  "paymentDeadline": "2026-08-25",
  "idempotencyKey": "account-123-invoice-456-sms-2026-08-25",
  "maxCredits": 1
}
```

`idempotencyKey` is optional in the API, but any automated integration should send one. Billabex
derives the communication identifier from it. Replaying the same key within the organization
returns the communication already recorded, with no new charge and no new call to the provider.

One key must always mean the same send intent. Use a new key for a new recipient, new content, or
a new deadline.

`maxCredits` is optional. If the rendered message costs more, the API refuses before any charge and
returns a `400`. Prefer setting it to the `credits` value the preview returned.

Known limitation: two strictly concurrent requests carrying the same key can both pass the initial
read before the first communication is recorded. Do not parallelise your own replays.

## Read the delivery back

```http
GET https://next.billabex.com/api/public/v1/accounts/ACCOUNT_ID/outgoing-message-communications/COMMUNICATION_ID
Authorization: Bearer YOUR_ACCESS_TOKEN
```

The response carries `channel`, `creditsCharged`, `deliveryStatus` and `statusHistory`, among
others. Provider webhooks stay internal to Billabex. Poll this endpoint for the latest known state.

## Errors

| Status | Meaning                                                              |
| ------ | -------------------------------------------------------------------- |
| `400`  | Invalid selection, recipient, address, letter settings, or cap        |
| `402`  | Not enough credits                                                    |
| `404`  | Account or communication not found                                    |
| `502`  | Provider result unknown, or send failed                               |

A `400` always carries a stable `code` field: branch on it rather than on the message. Exceeding
the cap returns `app-dunning.outgoing-message-communication.maximum-credits-exceeded` and a
`details` object holding `credits`, the real cost of the rendered message, and `maxCredits`, the
limit you had set. You can therefore decide to retry with a higher cap without previewing again.

When the provider result is unknown, credits stay charged because the message may have been
accepted. Do not start over with another key.

The details a letter needs are configured through
`PUT /api/public/v1/organizations/:organizationId/settings`, under `letterDetails`.

## MCP

The MCP server exposes the same flow:

| Tool                       | Scope       | Purpose                                     |
| -------------------------- | ----------- | ------------------------------------------- |
| `preview-outgoing-message` | `mcp:read`  | Compute the content and its cost            |
| `send-outgoing-message`    | `mcp:write` | Send, with a mandatory key                  |
| `get-outgoing-message`     | `mcp:read`  | Read the message and its delivery state     |

The MCP preview returns neither base64 PDF nor HTML. `send-outgoing-message` requires
`idempotencyKey` and states its cost and its irreversibility explicitly.

The older `/outgoing-letters` endpoints and the `preview-outgoing-letter` /
`send-outgoing-letter` tools remain available for existing integrations, but are deprecated.

---

Billabex developer portal. OpenAPI specification: https://developer.billabex.com/openapi.json.
Agent instructions: https://developer.billabex.com/llms.txt. Complete documentation: https://developer.billabex.com/llms-full.txt.
