---
name: send-sms-and-confirm-delivery
description: Send SMS through the Infobip SMS API, receive the delivery report, and resolve the status/error
  codes into a real delivery outcome.
api: openapi/infobip-sms-openapi.json
operations:
- send-sms-messages
- get-outbound-sms-message-delivery-reports-v3
- get-outbound-sms-message-logs-v3
- get-inbound-sms-messages
- preview-sms-message
provider: Infobip
generated: '2026-07-25'
method: generated
source: Grounded in operationIds verified in openapi/infobip-sms-openapi.json plus conventions/infobip-conventions.yml,
  errors/infobip-error-codes.yml and rate-limits/infobip-rate-limits.yml
---

# Send an SMS and confirm delivery

Send SMS through the Infobip SMS API, receive the delivery report, and resolve the status/error codes into a real delivery outcome.

## Ground rules for every Infobip call

- Base URL: `https://api.infobip.com` (an account may also be issued a personalized `https://{subdomain}.api.infobip.com` host shown in the web portal — either works).
- Auth header: `Authorization: App {API_KEY}`. Basic auth is only for the API-key-management endpoints. Every endpoint additionally requires at least one API scope (see `scopes/infobip-scopes.yml`).
- There is **no idempotency key**. Never blind-retry a send on a timeout or 5xx — re-read the outcome with the reports/logs operation for the returned `bulkId`/`messageId` first (`conventions/infobip-conventions.yml`).
- On `429` back off exponentially and honour `Retry-After`. Per-operation limits are published in the spec as `x-throttling-info` and collected in `rate-limits/infobip-rate-limits.yml`.
- HTTP errors use the Infobip `requestError.serviceException {messageId, text, validationErrors}` envelope (not RFC 9457) — see `errors/infobip-problem-types.yml`.
- A `200` does **not** mean delivered. Message outcome lives in the `status {groupId, groupName, id, name}` and `error {groupId, groupName, id, name, permanent}` objects; resolve them against `errors/infobip-error-codes.yml`. `permanent: true` means a retry can never succeed.
- Listing endpoints page with `page`/`size` (max 100) and return `{page, size, totalPages, totalResults}`; newer endpoints support `cursor`/`useCursor`.

## Steps
1. **(Optional) Preview the message** — `preview-sms-message` (`POST /sms/1/preview`) tells you how the text will be split into segments and which character set/transliteration applies before you spend money on it.
2. **Send** — `send-sms-messages` (`POST /sms/3/messages`). Put each recipient in `messages[].destinations[]`, the sender in `messages[].sender`, and set `messages[].webhooks.delivery.url` if you want a delivery-report callback instead of polling. The response returns `bulkId` and a `messages[]` array with one `messageId` + `status` per destination.
3. **Do not re-send on a timeout.** There is no idempotency key. If the call fails without a response, call `get-outbound-sms-message-logs-v3` (`GET /sms/3/logs`) filtered by your `bulkId` to see whether the send actually landed.
4. **Read the outcome.** Either consume the `receive-outbound-sms-message-reports-v3` webhook, or poll `get-outbound-sms-message-delivery-reports-v3` (`GET /sms/3/reports?bulkId=...`). Reports are consumed once when polled — a report returned to you will not be returned again.
5. **Interpret it.** `status.groupName` of `DELIVERED` is success; `PENDING` means still in flight; `UNDELIVERABLE`, `EXPIRED` and `REJECTED` are failures, and the `error` object names why (for example `EC_ABSENT_SUBSCRIBER`, `REJECTED_NOT_ENOUGH_CREDITS`). Only retry when `error.permanent` is `false`.
6. **Inbound replies** arrive on the `receive-inbound-sms-messages` webhook, or can be pulled with `get-inbound-sms-messages` (`GET /sms/1/inbox/reports`).

Scheduling: `get-scheduled-sms-messages`, `reschedule-sms-messages`, `get-scheduled-sms-messages-status` and `update-scheduled-sms-messages-status` operate on a whole `bulkId`.
