Skip to main content
Twilio

Twilio's Programmable SMS → the Naive phone primitive

Twilio gives an AI agent a real phone number and an SMS API: search for a number, buy it, register it for A2P 10DLC, then client.messages.create(...) to send and a webhook to receive. It does that job well, and the API is mature. But it is also a separate vendor account:
  • The number lives behind a Twilio Account SID + Auth Token, its own console, and its own billing — disconnected from wherever the agent’s cards, email, secrets, and KYC live.
  • A2P 10DLC compliance (Brand + Campaign registration, Messaging Services, sender pools) is yours to wire up and keep registered against your business entity.
  • “Who let this agent text a customer, and what else can it touch?” is answered in Twilio for messaging, and in unrelated systems for everything else. The rented DID has no shared accountability with the rest of the agent’s footprint.
Naive’s phone primitive gives the agent the same capability — provision a US number, send SMS, read inbound replies — but the number is carrier-registered (10DLC) against one governed identity:
  • The tenant user that owns the number is the same user that owns its cards, its email inboxes, its vault secrets, and its KYC.
  • The 10DLC carrier campaign is registered against the entity that user already formed and KYC’d — accountability is built into provisioning, not bolted on.
  • Whether an agent may send SMS — and whether provisioning a new number freezes for human approval — is decided by that user’s Account Kit and per-agent assignment at execution time.
This guide maps Twilio’s messaging API to Naive’s, shows the smallest working swap, and is explicit about what does not map yet.
Twilio is a trademark of Twilio Inc., used here for identification only. No endorsement or affiliation is implied.
Tested against: the Twilio Node helper library twilio v5.x against the Twilio REST API (version path 2010-04-01, base https://api.twilio.com, docs snapshot June 2026), and the Naive Node SDK @usenaive-sdk/server against the Naive API (base https://api.usenaive.ai/v1, docs snapshot June 2026).Version notes:
  • Twilio buys a number in two calls (availablePhoneNumbers(...).local.list to search, then incomingPhoneNumbers.create) and treats A2P 10DLC registration as a separate flow (TrustHub Brand/Campaign + a Messaging Service). Naive folds search + purchase + carrier registration into one phone.provision call.
  • Twilio identifies a number by its sid (PNxxxx) and accepts the E.164 string as from. Naive returns a number UUID (phone.id) plus the e164 string; phone.send takes the UUID as from.
  • Naive is US SMS only today. Voice, MMS send, WhatsApp, international numbers, short codes, and Twilio Verify have no equivalent — see what doesn’t map yet.
  • Prerequisite difference: Naive requires a completed LLC formation and the company EIN before the first phone.provision (the number is registered against your business entity). Twilio has no such requirement. This is the accountability trade — see the gaps section.

Concept map

Before / after: the core path

The path that matters for almost every SMS agent is get a number, then send a message from it. Here it is on both platforms.
The send shape lines up closely. The real differences to plan for:
  • One call to provision, not two-plus. Twilio is search → buy → register A2P → attach a Messaging Service. Naive’s provision buys the number and submits the 10DLC campaign together.
  • from is a UUID, not the E.164 string. Twilio’s from is the number itself. Naive’s phone.send takes the phone.id UUID; the e164 string is returned for display.
  • Outbound is carrier-gated on both — Naive surfaces it inline. Until the 10DLC campaign is active, Naive returns compliance_pending (no charge); poll phone.status() for progress.
  • The id is your identity, not a separate account. In Twilio the Account SID scopes Twilio. In Naive the same forUser(id) handle also owns the agent’s cards, email, vault, and KYC.

Receiving replies

Twilio delivers inbound SMS by POSTing to the number’s configured smsUrl (you reply with TwiML). Naive stores inbound messages against the receiving number; read them by polling, and react in real time with the sms.received event.
  • On Naive, inbound texts are captured automatically and queryable via phone.messages / phone.read — that path always works the moment a number is live.
  • The sms.received webhook is documented in the phone primitive for real-time reactions. The canonical emitted-event list is GET /v1/webhooks/event-types — treat that as the source of truth and confirm the event before depending on it (see gaps).
  • There is no synchronous TwiML reply. To answer an inbound message, call phone.send.

Minimal viable migration

The smallest swap that keeps a working agent running is just provision + send + read inbound.
1

Install the SDK and set your key

Set NAIVE_API_KEY (a server-side key from the dashboard).
2

Clear the provisioning prerequisites (one-time)

A Naive number is carrier-registered against your business entity, so before the first provision you need a completed LLC formation and the company EIN. If you do not have one, run KYCformation first. (Twilio has no equivalent gate — this is the accountability trade-off; see gaps.)
3

Swap number acquisition

Replace availablePhoneNumbers(...).local.list(...) + incomingPhoneNumbers.create(...) with a single client.phone.provision({ ein, area_code, label }). Keep the returned phone.id. Drop the separate A2P 10DLC Brand/Campaign and Messaging Service setup — provision submits the campaign for you.
4

Swap send

Replace client.messages.create({ from, to, body }) with client.phone.send({ from: phone.id, to, body }). Use the UUID as from. Handle compliance_pending by retrying once phone.status() shows the campaign active.
5

Swap inbound

Replace your smsUrl TwiML handler with client.phone.messages(phone.id) / client.phone.read(id) (and optionally the sms.received webhook). Reply by calling phone.send instead of returning TwiML.
6

Ship it

At this point you are off Twilio for the core US-SMS send/receive path. Everything below is upside, not a requirement.

Consolidate further once you’re on Naive

This is where the migration pays for itself. In Twilio, the Account SID isolates telephony and nothing else, and the number is an anonymous rented DID. On Naive, the unit of isolation is a tenant user, and the number is rooted in that identity’s verified, formed business.

Gain #1 — one identity across primitives

  • With Twilio, the agent’s numbers are an island behind an Account SID. With Naive, naive.forUser(acme.id) is a single handle to phone and cards and email and vault and KYC.
  • The number is not an anonymous DID — its 10DLC campaign is registered against the entity that identity already formed and verified. Tear the customer down from one place; sibling tenants can never read each other’s messages, cards, or secrets.

Gain #2 — execution-time permission enforcement

  • Whether an agent may send SMS at all is per-agent assignment on the number, and whether provisioning a new number freezes for human review is policy on the Account Kit — not a check you hand-write and not a Twilio API-Key scope you manage separately.
  • The agent’s code is identical for every tier — client.phone.send({ ... }). Whether the call runs is decided at execution time:
    • An agent without send_sms on the number is refused with forbidden, with no code change on your side.
    • phone.requiresApproval: true freezes a provision until a human approves — the API replays it only after approval. Sending SMS is a routine action and is not approval-gated.

Gain #3 — unified accountability

  • Every provision, send, and inbound reply for a customer lands in one per-user activity log — alongside their card, email, vault, and KYC events, not in a separate Twilio console:
  • That is the question that is hard to answer when SMS lives in Twilio, cards live in Stripe, and secrets live somewhere else. Under Naive it is a single query.

What does not map yet

A migration guide that hides gaps is worse than none. The core path (provision → send → read inbound) maps cleanly for US SMS, but the following Twilio capabilities have no direct equivalent on Naive’s phone primitive today. Check this list against your app before you commit.
If your agent needs voice, MMS/WhatsApp send, international numbers, Twilio Verify, Messaging Services, or a synchronous TwiML reply, those are the gaps most likely to matter — Naive does US SMS over a single 10DLC long code today. Equally, note the prerequisite inversion: Twilio rents you a number with no identity check, while Naive requires a formed, KYC’d entity first and registers the number against it. That requirement is the accountability gain — but it is a real step you must complete before your first provision.

Where to go next

  • phone primitive — full provision/send/receive lifecycle + 10DLC gating
  • phone SDK sub-client — typed method signatures
  • phone CLI — provision, send, messages, assign, release
  • Webhooks — outbound event subscriptions (confirm sms.received via GET /v1/webhooks/event-types)
  • Account Kits and Approvals — the policy model behind execution-time governance and provision approval
  • Verification and Formation — the KYC + entity that the carrier registration (and the consolidation) hangs off
  • Tenant users — the identity that owns the number, cards, email, and vault