MMS, scheduling, and validity
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
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
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
sendAtthrowsUnsupportedFieldError(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.
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
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.