> ## Documentation Index
> Fetch the complete documentation index at: https://voyant.travel/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Messaging

> Send SMS and email, and run phone or email verification flows, through Voyant.

Voyant handles the outbound communication a travel app relies on: SMS to travelers, transactional email, and one-time-code verification. All three are groups on the [Cloud SDK](/docs/sdks/overview) client.

```ts theme={null}
import { createVoyantCloudClient } from "@voyant-travel/cloud-sdk";

const client = createVoyantCloudClient({ apiKey: process.env.VOYANT_API_KEY! });
```

## SMS

The `sms` group sends text messages and lists your numbers and message history.

```ts theme={null}
// List the phone numbers available to your account
const numbers = await client.sms.listPhoneNumbers();

// Send a message
const message = await client.sms.sendMessage({
  to: "+14155551234",
  body: "Your transfer is confirmed for 09:00.",
});

// List recent messages
const history = await client.sms.listMessages();
```

| Method                   | Scope                |
| ------------------------ | -------------------- |
| `sms.listPhoneNumbers()` | `phone-numbers:read` |
| `sms.listMessages()`     | `sms:read`           |
| `sms.sendMessage(input)` | `sms:send`           |

Key types: `SendSmsInput`, `SmsMessageSummary`, `PhoneNumberSummary`, `SmsMessageStatus`, `PhoneNumberStatus`.

## Email

The `email` group sends transactional email and reads message records.

```ts theme={null}
const delivery = await client.email.sendMessage({
  idempotencyKey: crypto.randomUUID(), // keep this stable for every retry
  to: "traveler@example.com",
  subject: "Your itinerary",
  html: "<p>Thanks for booking. Your documents are attached.</p>",
});

const sent = await client.email.listMessages();
const detail = await client.email.getMessage(delivery.message.id);
```

Email sends are admitted by an organization-scoped durable ledger. Retrying the
same `Idempotency-Key` with the exact same normalized request returns the
persisted canonical `EmailDeliveryResult` without another provider acceptance.
Reusing it with a changed recipient, body, reply-to, or attachment fails with
`409`. Keep the key stable for one logical attempt; do not generate it inside a
transport retry callback.

The result includes `protocolVersion`, `operationId`, opaque `backendIdentity`
and `accountIdentity`, and `acceptedCount: 1`. A worker can bind these fields to
the safe, non-delivering `/email/v1/idempotency-conformance` preflight so a
backend credential swap fails closed after restart.

| Method                     | Scope         |
| -------------------------- | ------------- |
| `email.listMessages()`     | `emails:read` |
| `email.getMessage(id)`     | `emails:read` |
| `email.sendMessage(input)` | `emails:send` |

Key types: `SendEmailInput`, `EmailDeliveryResult`, `EmailMessageSummary`, `EmailMessageStatus`.

## Verification

The `verification` group runs one-time-code flows over a channel such as SMS. You start an attempt, then check the code the user entered.

```ts theme={null}
// Start a verification attempt
const attempt = await client.verification.start({
  to: "+14155551234",
  channel: "sms",
});

// Check the code the user submitted
const result = await client.verification.check({
  to: "+14155551234",
  code: "123456",
});

if (result.status === "approved") {
  // proceed
}
```

| Method                        | Scope                |
| ----------------------------- | -------------------- |
| `verification.start(input)`   | `verification:start` |
| `verification.check(input)`   | `verification:check` |
| `verification.listAttempts()` | `verification:read`  |

Key types: `StartVerificationInput`, `CheckVerificationInput`, `VerificationCheckResult`, `VerificationChannel`, `VerificationAttemptStatus`.

## Errors

Every method throws a `VoyantApiError` with `status`, `requestId`, and `body` on failure. See [Errors and transport](/docs/sdks/errors-and-transport).
