Skip to main content

Send a transactional SMS

A transactional SMS is sent one at a time, when your system decides: a booking confirmation, an appointment reminder, a pickup code. It is billed at the same rate as your campaigns and counts towards the same daily cap.

Use this API only for messages that respond to an action of the recipient. Promotional messages (offers, sales follow-ups) go through campaigns, which handle the STOP mention and unsubscribes. A recipient who unsubscribed from your campaigns still receives your transactional messages.

Prerequisites​

  • A secret API key with the send_transactional_messages permission (and view_transactional_messages to read sent messages). See Authentication.
  • Credits, or an SMS allowance included in your plan.
  • Optional: a verified SMS sender (Settings › Senders). Without one, the message is sent from the platform's short code.

Send​

curl https://api.metaventus.com/v1/transactional/sms \
-H "X-API-Key: YOUR_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: booking-20481" \
-d '{
"to": "+33612345678",
"from": "LEBISTROT",
"body": "Your table for 4 is confirmed tonight at 8pm. Thank you!",
"reference": "booking-20481"
}'
FieldRequiredDescription
toyesRecipient number in international format (+33612345678). A national number is read as French.
bodyyesThe text, sent as is. 9 SMS at most (see Length and cost).
fromnoA verified SMS sender of your account. Without from: your default sender, otherwise the platform's.
referencenoYour own identifier (booking number…), up to 128 characters. Returned as is, filterable.

Response 202 Accepted: the message is accepted, billed and on its way.

{
"id": "922c2bfd-32ec-4773-b348-63967731e3a7",
"channel": "sms",
"to": "+33612345678",
"from": "LEBISTROT",
"body": "Your table for 4 is confirmed tonight at 8pm. Thank you!",
"segments": 1,
"encoding": "gsm7",
"country_code": "33",
"status": "queued",
"reference": "booking-20481",
"created_at": "2026-10-14T09:00:00Z",
"updated_at": "2026-10-14T09:00:00Z"
}

202 is not an error: it is the normal response to a send.

Never send twice​

Send an Idempotency-Key header unique to each message (your booking number, for instance). If your request times out and you retry with the same key, the first message is returned with a 200 and the Idempotent-Replayed: true header: nothing is sent again to the recipient, nothing is billed again.

Reusing a key for a different message (other recipient or other text) is rejected (422 idempotency_key_reused).

Length and cost​

An SMS is billed by the number of SMS the carrier transports, at the rate of the recipient's country — the same as your campaigns. The response shows what was counted (segments, encoding, country_code).

Alphabet1 SMSBeyond, per SMS
GSM-7 (Latin letters, é, è, à…)160 characters153 characters
Unicode (as soon as one character is outside GSM-7: ç, ê, â, ô, À, emoji…)70 characters67 characters

The characters € [ ] { } ^ ~ | \ count twice in GSM-7. A message is limited to 9 SMS; beyond that it is rejected (400 body_too_long) and nothing is billed.

Track delivery​

curl https://api.metaventus.com/v1/transactional/messages/922c2bfd-32ec-4773-b348-63967731e3a7 \
-H "X-API-Key: YOUR_SECRET_KEY"
statusMeaning
queuedAccepted and billed, being handed to the carrier.
sentHanded to the carrier (sent_at).
deliveredReceived by the handset (delivered_at).
failedNot delivered — see error_code.
error_codeCauseBilled
rejectedRejected by the carrier (number or content).No, refunded
not_sentNever handed to the carrier.No, refunded
undeliveredTransported but not received (handset off, out of coverage, inactive number).Yes

The delivery receipt usually arrives within seconds; the carrier retries delivery for up to 48 hours. The status is refreshed about every two minutes: poll it sparingly.

List messages​

curl "https://api.metaventus.com/v1/transactional/messages?status=failed&limit=50" \
-H "X-API-Key: YOUR_SECRET_KEY"

Filters: status, reference, to. Messages come newest first, limit from 1 to 100 (default 50). When has_more is true, request the next page with starting_after=<id of the last message received>.

Rejections​

A rejected request is neither sent nor billed. The body carries a stable code to branch your logic on, and a human-readable sentence in error.

StatuscodeCauseWhat to do
400invalid_to, invalid_body, body_too_long, invalid_referenceInvalid requestFix it
402insufficient_creditsInsufficient balance (details.credits_needed, details.wallet_balance)Top up, then retry
403account_suspendedSubscription suspendedSettle it
422sender_not_allowedfrom is not a verified sender of the accountFix from or remove it
422idempotency_key_reusedKey already used for another messageNew key
429daily_limit_reachedYour plan's SMS cap is reached for the period (details.limit, details.used)Wait for the next day, or upgrade
503temporarily_unavailableTransient incidentRetry with the same Idempotency-Key
{
"error": "Plafond de votre offre atteint : 100 SMS par jour, il en reste 1, ce message en compte 2.",
"code": "daily_limit_reached",
"details": { "limit": 100, "used": 99, "period": "daily", "segments": 2 }
}

The error sentence is in French; rely on code.

The daily cap is shared with your campaigns, automations and appointment reminders: there is one SMS allowance, whatever the sending channel.