twilio-client (Twilio Messaging end-to-end)
Scope — the package is wider than this recipe. Since 0.2.0 the
package ships the whole merged surface: 97 API modules across
Messaging v1 and API v2010 — voice/calls, recordings, conferences,
queues, applications, SIP, usage records, and the rest. This recipe
covers the messaging path only (the modules named below), which
is the part with a verified request path. Everything else is
generated and typechecks, but is undocumented here and
runtime-unverified: read Twilio's REST docs for those, and expect no
guidance from this skill. Nothing outside messaging should be
presented to a user as a supported capability on the strength of
this file.
Motoko bindings for Twilio Messaging,
generated from Twilio's two upstream OpenAPI specs (Messaging V1 +
API v2010) merged into one client. Two concerns are kept distinct
but both handled here:
- Configuration — Messaging Services, A2P 10DLC brand /
campaign / number-assignment, toll-free verification, sender-pool
settings, link shortening, domain certs. All on the v1 surface
(
MessagingV1*Api), hosted at messaging.twilio.com.
- Delivery —
createMessage / fetchMessage / listMessage,
MMS media, user-defined message events, owning + buying numbers.
On the v2010 surface (Api20100401*Api), hosted at
api.twilio.com.
The two hosts are routed per operation at codegen time — callers
see one Config and one credential pair, not two.
Trigger phrases
Twilio, SMS, MMS, text message, send a text, messaging service,
A2P 10DLC, toll-free verification, brand registration, brand
vetting, alpha sender, channel sender, short code, US App to
Person, US A2P usecase, link shortening, messaging domain cert,
phone number purchase, incoming phone number, available phone
numbers, Twilio deactivation list.
One auth, two hosts
Both halves of the client use the same HTTP Basic credential
pair — Account SID + Auth Token in dev, or an API-Key SID +
Secret in production (API Keys are revocable + scoped; preferred
for production canisters). Set it once via Config.auth; the
client routes each operation to the right host on its own.
import { defaultConfig } "mo:twilio-client/Config";
let cfg = {
defaultConfig with
auth = ?#basicAuth { user = accountSid; password = authToken };
};
Config.baseUrl is unused in this client — every operation
carries a hardcoded host (messaging.twilio.com for v1 config,
api.twilio.com for v2010 delivery). Don't set it. Stable
canister variables for accountSid / authToken, not source.
Sending an SMS
import MessageApi "mo:twilio-client/Apis/Api20100401MessageApi";
// Send to one recipient from one Twilio number.
let msg = await* MessageApi.createMessage(
cfg,
accountSid, // accountSid
"+15558675309", // to
"", // statusCallback
"", // applicationSid
0.0, // maxPrice (0 = no cap)
false, // provideFeedback
0, // attempt
0, // validityPeriod (seconds; 0 = default)
false, // forceDelivery
#retain, // contentRetention
#retain, // addressRetention
false, // smartEncoded
[], // persistentAction
#free, // trafficType
false, // shortenUrls
#fixed, // scheduleType
"", // sendAt (only for scheduled)
false, // sendAsMms
"", // contentVariables
#enable, // riskCheck
"+15551234567", // from (a Twilio number you own)
"", // fallbackFrom
"", // messagingServiceSid (use EITHER from OR this)
"Hello, world!", // body
[], // mediaUrl (set for MMS)
"", // contentSid (Content API templates)
);
// msg.sid, msg.status, msg.errorCode, ...
For MMS: pass mediaUrl = ["https://example.com/image.jpg"] and
sendAsMms = true.
To send via a configured Messaging Service (recommended for any
US-bound production traffic — see A2P below), leave from = ""
and set messagingServiceSid instead.
Production sender setup (US A2P 10DLC)
Before any US long-code can send to US destinations, three
resources must exist in this order. Without all three, US
carriers reject the message:
- Brand registration
MessagingV1BrandRegistrationApi.createBrandRegistrations —
references Trust Hub customerProfileBundleSid +
a2PProfileBundleSid (created out-of-band). Pass mock = true during dev (skips the registration fee). Status starts
PENDING, settles to APPROVED or FAILED over hours-to-days;
fails if business info is incomplete, inconsistently
formatted, or doesn't match registry data.
- A2P campaign
MessagingV1UsAppToPersonApi.createUsAppToPerson — references
the messaging service AND the brand. Most onboarding failures
land here. T-Mobile rejects campaigns whose messageFlow
doesn't describe opt-in or whose messageSamples don't match
the declared usAppToPersonUsecase.
- Phone-number-to-service assignment
MessagingV1PhoneNumberApi.createPhoneNumber(cfg, serviceSid, phoneNumberSid) — a number can live in exactly one messaging
service at a time; reassignment needs deletePhoneNumber
first.
2026-06-30 deadline: campaign registrations without working
privacyPolicyUrl and termsAndConditionsUrl start hard-400ing
on that date. Both are positional args on createUsAppToPerson;
passing "" 400s. URLs must resolve to public HTTPS pages —
Twilio fetches them during registration. Toll-free numbers use
a separate flow (MessagingV1TollfreeVerificationApi), not A2P.
Number management
Api20100401IncomingPhoneNumberApi — list / fetch / update /
release numbers you own (delete = release back to Twilio).
Api20100401IncomingPhoneNumberLocal / Mobile / TollFreeApi —
type-specific operations (e.g. listing only TollFree numbers).
Api20100401AvailablePhoneNumberCountryApi — browse numbers
available for purchase by country.
Api20100401DependentPhoneNumberApi — numbers tied to a
specific street address (E911 compliance).
Errors and replication
- Methods return the decoded response record on 2xx and
throw Error.reject("HTTP <status>: …") on 4xx / 5xx. Wrap in
try { … } catch (e) { Error.message(e) } to inspect Twilio's
error body (code, message, more_info, status).
Common codes: 21408 region not permissioned, 21703 sender-pool
exhausted, 21704 service has no numbers, 21714 pool size
capped.
- Writes (
create* / update* / delete*) hit non-idempotent
Twilio endpoints — leave is_replicated at its default (null,
replicated) so IC consensus dedups retries. Reads (list* /
fetch*) can take is_replicated = ?false for cheaper outcalls
when a single-node view is fine.
- Paginated lists carry
meta.next_page_url /
meta.previous_page_url; pageSize caps at 1000, default 50.
Field gotchas
usecase (on createService) is Text, not a variant. Valid
values: notifications, marketing, verification,
discussion, poll, undeclared. Anything else 400s.
usAppToPersonUsecase is a different enum, brand-tier
dependent — query
UsAppToPersonUsecaseApi.fetchUsAppToPersonUsecase for the
list available to a given brand.
- HTTP method variants are lowercase:
#get, #post, #put,
#delete.
xTwilioApiVersion parameter (on *UsAppToPerson methods) —
pass "" unless Twilio support specifically asks.
- Per-sender throughput: long-code 1 MPS, short-code 100 MPS,
international long-code 10 MPS. Per-number MPS cannot be raised;
scale by adding numbers to the service's sender pool.
stickySender / areaCodeGeomatch are US + Canada only.
1---2name: extension-twilio-messaging3description: Use the `twilio-client` mops package for any request involving Twilio messaging — sending SMS / MMS, configuring a Messaging Service, A2P 10DLC brand registration / campaigns, toll-free verification, alpha / channel senders, short codes, link shortening, domain certs, owning / browsing phone numbers, or the Twilio deactivation list. One package covers both halves of Twilio's messaging stack — configuration (Messaging API v1) and delivery (API v2010 Messages) — under one `Config`. Raw `ic.http_request` calls to `messaging.twilio.com` or `api.twilio.com` are the wrong path: they bypass the generated typing, per-operation host routing, and Basic-Auth header construction.4---56# twilio-client (Twilio Messaging end-to-end)78> **Scope — the package is wider than this recipe.** Since 0.2.0 the9> package ships the *whole* merged surface: **97 API modules** across10> Messaging v1 and API v2010 — voice/calls, recordings, conferences,11> queues, applications, SIP, usage records, and the rest. This recipe12> covers the **messaging** path only (the modules named below), which13> is the part with a verified request path. Everything else is14> generated and typechecks, but is undocumented here and15> runtime-unverified: read Twilio's REST docs for those, and expect no16> guidance from this skill. Nothing outside messaging should be17> presented to a user as a supported capability on the strength of18> this file.1920Motoko bindings for [Twilio Messaging](https://www.twilio.com/docs/messaging),21generated from Twilio's two upstream OpenAPI specs (Messaging V1 +22API v2010) merged into one client. Two concerns are kept distinct23but both handled here:2425- **Configuration** — Messaging Services, A2P 10DLC brand /26 campaign / number-assignment, toll-free verification, sender-pool27 settings, link shortening, domain certs. All on the v1 surface28 (`MessagingV1*Api`), hosted at `messaging.twilio.com`.29- **Delivery** — `createMessage` / `fetchMessage` / `listMessage`,30 MMS media, user-defined message events, owning + buying numbers.31 On the v2010 surface (`Api20100401*Api`), hosted at32 `api.twilio.com`.3334The two hosts are routed per operation at codegen time — callers35see one `Config` and one credential pair, not two.3637## Trigger phrases3839Twilio, SMS, MMS, text message, send a text, messaging service,40A2P 10DLC, toll-free verification, brand registration, brand41vetting, alpha sender, channel sender, short code, US App to42Person, US A2P usecase, link shortening, messaging domain cert,43phone number purchase, incoming phone number, available phone44numbers, Twilio deactivation list.4546## One auth, two hosts4748Both halves of the client use the **same** HTTP Basic credential49pair — Account SID + Auth Token in dev, or an API-Key SID +50Secret in production (API Keys are revocable + scoped; preferred51for production canisters). Set it once via `Config.auth`; the52client routes each operation to the right host on its own.5354```motoko55import { defaultConfig } "mo:twilio-client/Config";5657let cfg = {58 defaultConfig with59 auth = ?#basicAuth { user = accountSid; password = authToken };60};61```6263`Config.baseUrl` is **unused** in this client — every operation64carries a hardcoded host (`messaging.twilio.com` for v1 config,65`api.twilio.com` for v2010 delivery). Don't set it. Stable66canister variables for `accountSid` / `authToken`, not source.6768## Sending an SMS6970```motoko71import MessageApi "mo:twilio-client/Apis/Api20100401MessageApi";7273// Send to one recipient from one Twilio number.74let msg = await* MessageApi.createMessage(75 cfg,76 accountSid, // accountSid77 "+15558675309", // to78 "", // statusCallback79 "", // applicationSid80 0.0, // maxPrice (0 = no cap)81 false, // provideFeedback82 0, // attempt83 0, // validityPeriod (seconds; 0 = default)84 false, // forceDelivery85 #retain, // contentRetention86 #retain, // addressRetention87 false, // smartEncoded88 [], // persistentAction89 #free, // trafficType90 false, // shortenUrls91 #fixed, // scheduleType92 "", // sendAt (only for scheduled)93 false, // sendAsMms94 "", // contentVariables95 #enable, // riskCheck96 "+15551234567", // from (a Twilio number you own)97 "", // fallbackFrom98 "", // messagingServiceSid (use EITHER from OR this)99 "Hello, world!", // body100 [], // mediaUrl (set for MMS)101 "", // contentSid (Content API templates)102);103// msg.sid, msg.status, msg.errorCode, ...104```105106For MMS: pass `mediaUrl = ["https://example.com/image.jpg"]` and107`sendAsMms = true`.108109To send via a configured Messaging Service (recommended for any110US-bound production traffic — see A2P below), leave `from = ""`111and set `messagingServiceSid` instead.112113## Production sender setup (US A2P 10DLC)114115Before any US long-code can send to US destinations, **three116resources must exist in this order**. Without all three, US117carriers reject the message:1181191. **Brand registration**120 `MessagingV1BrandRegistrationApi.createBrandRegistrations` —121 references Trust Hub `customerProfileBundleSid` +122 `a2PProfileBundleSid` (created out-of-band). Pass `mock =123 true` during dev (skips the registration fee). Status starts124 `PENDING`, settles to `APPROVED` or `FAILED` over hours-to-days;125 fails if business info is incomplete, inconsistently126 formatted, or doesn't match registry data.1272. **A2P campaign**128 `MessagingV1UsAppToPersonApi.createUsAppToPerson` — references129 the messaging service AND the brand. Most onboarding failures130 land here. T-Mobile rejects campaigns whose `messageFlow`131 doesn't describe opt-in or whose `messageSamples` don't match132 the declared `usAppToPersonUsecase`.1333. **Phone-number-to-service assignment**134 `MessagingV1PhoneNumberApi.createPhoneNumber(cfg, serviceSid,135 phoneNumberSid)` — a number can live in exactly one messaging136 service at a time; reassignment needs `deletePhoneNumber`137 first.138139**2026-06-30 deadline**: campaign registrations without working140`privacyPolicyUrl` *and* `termsAndConditionsUrl` start hard-400ing141on that date. Both are positional args on `createUsAppToPerson`;142passing `""` 400s. URLs must resolve to public HTTPS pages —143Twilio fetches them during registration. Toll-free numbers use144a separate flow (`MessagingV1TollfreeVerificationApi`), not A2P.145146## Number management147148- `Api20100401IncomingPhoneNumberApi` — list / fetch / update /149 release numbers you own (delete = release back to Twilio).150- `Api20100401IncomingPhoneNumberLocal / Mobile / TollFreeApi` —151 type-specific operations (e.g. listing only TollFree numbers).152- `Api20100401AvailablePhoneNumberCountryApi` — browse numbers153 available for purchase by country.154- `Api20100401DependentPhoneNumberApi` — numbers tied to a155 specific street address (E911 compliance).156157## Errors and replication158159- Methods return the decoded response record on 2xx and160 `throw Error.reject("HTTP <status>: …")` on 4xx / 5xx. Wrap in161 `try { … } catch (e) { Error.message(e) }` to inspect Twilio's162 error body (`code`, `message`, `more_info`, `status`).163 Common codes: 21408 region not permissioned, 21703 sender-pool164 exhausted, 21704 service has no numbers, 21714 pool size165 capped.166- Writes (`create*` / `update*` / `delete*`) hit non-idempotent167 Twilio endpoints — leave `is_replicated` at its default (`null`,168 replicated) so IC consensus dedups retries. Reads (`list*` /169 `fetch*`) can take `is_replicated = ?false` for cheaper outcalls170 when a single-node view is fine.171- Paginated lists carry `meta.next_page_url` /172 `meta.previous_page_url`; `pageSize` caps at 1000, default 50.173174## Field gotchas175176- `usecase` (on `createService`) is `Text`, not a variant. Valid177 values: `notifications`, `marketing`, `verification`,178 `discussion`, `poll`, `undeclared`. Anything else 400s.179- `usAppToPersonUsecase` is a *different* enum, brand-tier180 dependent — query181 `UsAppToPersonUsecaseApi.fetchUsAppToPersonUsecase` for the182 list available to a given brand.183- HTTP method variants are lowercase: `#get`, `#post`, `#put`,184 `#delete`.185- `xTwilioApiVersion` parameter (on `*UsAppToPerson` methods) —186 pass `""` unless Twilio support specifically asks.187- Per-sender throughput: long-code 1 MPS, short-code 100 MPS,188 international long-code 10 MPS. Per-number MPS cannot be raised;189 scale by adding numbers to the service's sender pool.190- `stickySender` / `areaCodeGeomatch` are US + Canada only.