TLDR: US A2P SMS is carrier-blocked without 10DLC registration. Twilio's campaign wizard has three silent failure modes — here's every one, plus the CDP trick for the nastiest.

the setup

I built a birth announcement SMS app — an app that lets a new parent broadcast a birth announcement via SMS to a gated family feed.

Cute idea, right?

Except the moment you try to deliver US-bound application texts, the carriers block you by default. Twilio calls this A2P 10DLC (Application-to-Person, 10-digit long code) — a mandatory carrier registration system. No registration, no delivery. Just silence.

So. Had to register.

the wall

Brand registration felt easy. "Registered" came back in about a minute. I assumed campaign approval would go the same way.

It didn't.

Campaign rejected.

No real reason in the rejection itself. I had to go hunting.

what I tried that didn't work

First — I couldn't even get back to the campaign. I pasted the detail URL into Arc (my browser) directly. Instant 403. The Twilio console is an SPA (single-page app — it routes client-side, not via traditional page loads), so hard-navigating to a campaign URL just… breaks. You have to reach it through the left-nav: Messaging → Regulatory Compliance → Campaigns, then click the row anchor. That is the only path that works.

Second — the edit modal wouldn't save. I needed to re-enter the privacy and terms URLs. Typed them in. Hit Update. Button stayed greyed out.

This one burned time.

The Twilio console uses React controlled inputs. I drive the browser via arc-cdp eval (Chrome DevTools Protocol — scripted browser control, no new tab). The thing is, React keeps an internal value tracker, and a plain .value = '...' + dispatching an input event is not enough — React sees no state change, and the Update button never enables.

The actual fix:

const el = document.querySelector('input[name="privacyPolicyUrl"]');
const nativeSetter = Object.getOwnPropertyDescriptor(
  window.HTMLInputElement.prototype, 'value'
).set;
nativeSetter.call(el, 'https://[your-domain]/privacy');
el._valueTracker.setValue('reset_' + Math.random());
el.dispatchEvent(new Event('input',  { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
el.dispatchEvent(new Event('blur',   { bubbles: true }));

Every line matters. Skip any one of them and the button stays grey.

Third — the actual reject reason. The app had no privacy policy. 10DLC campaign approval requires one. I built real /privacy and /terms pages: what data is collected, how it's used, confirmed it's never sold or used for marketing. Standard content — but it has to exist before you even submit.

the fix that worked

  1. Built real /privacy and /terms pages — not placeholders.
  2. Navigated to the campaign via left-nav only — never a direct URL.
  3. Re-entered the privacy + terms URLs with the _valueTracker reset so React actually registered the change.
  4. Resubmitted.

Campaign approved.

why this matters to me

If you're building any US SMS feature — birth announcements, alerts, reminders, anything — you will hit this wall. Twilio's docs cover the registration concept fine. They don't tell you about the SPA navigation trap, the React input trick, or the privacy-page hard requirement.

Know those three things going in and you save yourself hours of silent failure.

P.S. There's a fourth gotcha I almost buried. The phone number purchase modal has a "I agree to comply with Emergency calling terms" checkbox. If you miss it, the Buy button silently no-ops — the modal closes, no number purchased, no error message. Looked like a success. Wasn't. Check the checkbox.