Features8 min

Send an SMS or a letter

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

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

POST [baseURL]/api/public/v1/accounts/ACCOUNT_ID/outgoing-message-communications/preview
Authorization: Bearer YOUR_ACCESS_TOKEN
Content-Type: application/json

SMS example:

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

Letter example:

{
  "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:

{
  "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

POST [baseURL]/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:

{
  "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

GET [baseURL]/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.