---
title: "MMS, scheduling, and validity"
description: "Send media, schedule a message, set how long a provider keeps trying, and set a per-message status callback, with each provider's limits."
---

Media, scheduling, expiry, and per-message status callbacks depend on the provider. SMS SDK checks each field against the adapter's capabilities and throws `UnsupportedFieldError` before any request instead of dropping it.

## What each provider supports

| Field | Twilio | Telnyx | Plivo | Vonage |
| --- | --- | --- | --- | --- |
| `mediaUrls` (MMS) | ✅ up to 10 URLs | ✅ | ✅ | ❌ |
| `sendAt` (scheduling) | ✅ Messaging Service sender only | ✅ | ❌ | ❌ |
| `validityPeriodSec` | 1 to 36000 s | ❌ | 5 to 10799 s | 20 to 604800 s |
| `webhookUrl` (status callback) | ✅ | ✅ | ✅ | ✅ JWT auth only |

Read the same data at runtime with `sms.capabilities()`.

## Send media

```ts
import { sms } from "./sms";

await sms.send({
  to: "+14155550123",
  body: "Here is your boarding pass.",
  mediaUrls: ["https://example.com/passes/abc123.png"],
});
```

Each URL must be absolute `http` or `https`, and the provider must be able to fetch it. The body may be empty when `mediaUrls` is set. MMS depends on the destination country and the sender. Many countries outside the US and Canada do not support it, and the provider reports that as a rejection.

## Schedule a message

```ts
import { sms } from "./sms";

await sms.send({
  to: "+14155550123",
  from: { messagingService: "MG00000000000000000000000000000000" },
  body: "Your appointment starts in one hour.",
  sendAt: new Date("2026-12-01T08:00:00Z"),
});
```

The provider holds the message and sends it at `sendAt`. `send()` resolves when the provider accepts the scheduled message, usually with `delivery: "queued"`.

- Twilio schedules only from a Messaging Service sender. Any other sender with `sendAt` throws `UnsupportedFieldError` (`field: "sendAt"`). Twilio rejects send times outside its own minimum and maximum lead times.
- Telnyx schedules from any sender type.
- SMS SDK has no cancel call. Cancel a scheduled message in the provider's API or console.

## Set a validity period

`validityPeriodSec` sets how long the provider may keep trying to deliver before the message expires.

```ts
import { sms } from "./sms";

await sms.send({
  to: "+14155550123",
  body: "Your sign-in code is 482913. It expires in 10 minutes.",
  validityPeriodSec: 600,
});
```

A value outside the adapter's range throws `InvalidMessageError` (`field: "validityPeriodSec"`). Telnyx has no such field, so any value throws `UnsupportedFieldError`.

## Set a status callback per message

```ts
import { sms } from "./sms";

await sms.send({
  to: "+14155550123",
  body: "Your order has shipped.",
  webhookUrl: "https://example.com/webhooks/sms/status",
});
```

The provider posts status updates for this message to `webhookUrl` instead of the URL configured on your number or account. On Vonage this needs JWT auth (`applicationId` and `privateKey`), because Vonage's Basic auth does not support webhooks.

## With fallback

Fallback skips adapters that cannot send a field you set. The primary adapter must support every field. See [Retries and fallback](/sending/retries-and-fallback).

## Next steps

- [Providers overview](/providers/overview)
- [Delivery status webhooks](/receiving/delivery-status-webhooks)
