Skip to content

IR Brief — Fix 1: SMS Country Code & E.164 Assembly

SURFACE: ir.ucca.online (ucca-ir) DO NOT TOUCH: ucca-keys, ucca-engine, rtopacks.com.au, ucca.online, any other Worker or repo


Problem

SMS is not sending. The mobile field collects raw input (e.g. 0422334489) and passes it to Twilio without a country code. Twilio requires E.164 format (+61422334489). Result: every SMS attempt silently fails.

What to build

1. Frontend — Two-field phone input

Replace the single mobile <input> with a two-field compound input:

[+61 ▾]  [mobile number          ]

Country code selector: - Rendered as a styled <select> or custom dropdown - Pre-populated with a curated list of countries (at minimum: AU +61, NZ +64, US +1, GB +44, SG +65, IN +91, JP +81, DE +49, FR +33, CA +1) - Default selected via IP geo — see Worker endpoint below - Visually: flag emoji + dial code in the selector, e.g. 🇦🇺 +61 - The selector sits flush left, the number field fills remaining width — both fields share one visual container styled to match existing inputs (purple/charcoal aesthetic)

Number field: - type="tel" - placeholder adapts by country — AU: 04XX XXX XXX, US: (555) 000-0000 - Strip all non-numeric characters client-side before submission (spaces, dashes, parens) - For AU (+61): strip leading 0 before concatenation — 0422334489422334489+61422334489 - For all other countries: concatenate dial code + cleaned number as-is

Assembly (client-side, before POST):

function assembleE164(dialCode, rawNumber) {
  const digits = rawNumber.replace(/\D/g, '');
  // AU-specific: strip leading zero
  const cleaned = dialCode === '61' && digits.startsWith('0')
    ? digits.slice(1)
    : digits;
  return `+${dialCode}${cleaned}`;
}

Send the assembled mobile value (E.164 string) to the Worker, not the raw parts.


2. New Worker endpoint — /api/geo

Add to ucca-ir Worker (same wrangler.toml, same repo, new route).

// GET /api/geo
// Returns country dial code based on CF-IPCountry header
export async function handleGeo(request) {
  const country = request.headers.get('CF-IPCountry') || 'AU';

  const dialCodes = {
    AU: '61', NZ: '64', US: '1', GB: '44',
    SG: '65', IN: '91', JP: '81', DE: '49',
    FR: '33', CA: '1', ZA: '27', HK: '852',
    MY: '60', ID: '62', PH: '63', TH: '66',
    CN: '86', KR: '82', AE: '971'
  };

  const dialCode = dialCodes[country] || '61';

  return new Response(JSON.stringify({ country, dialCode }), {
    headers: {
      'Content-Type': 'application/json',
      'Cache-Control': 'no-store'
    }
  });
}

Route: Add GET /api/geo to the Worker router.
CORS: Allow from ir.ucca.online origin only.

Frontend calls this on page load:

fetch('/api/geo')
  .then(r => r.json())
  .then(({ dialCode }) => {
    // Set the selector default value
    document.getElementById('dial-code-select').value = dialCode;
  })
  .catch(() => {
    // Fallback: default to AU (+61) silently
  });


3. Worker — Registration handler update

In the existing registration POST handler (/api/register or equivalent):

  1. Accept mobile as a pre-assembled E.164 string from the client
  2. Add server-side validation before Twilio call:
function isValidE164(mobile) {
  return /^\+[1-9]\d{7,14}$/.test(mobile);
}

if (!isValidE164(mobile)) {
  return new Response(JSON.stringify({ 
    error: 'Invalid mobile number format' 
  }), { status: 400 });
}
  1. Pass the validated E.164 string directly to Twilio — no transformation in the Worker.

4. Twilio call — confirm it's clean

The Twilio SMS dispatch should look like:

const twilioBody = new URLSearchParams({
  To: mobile,        // already E.164, e.g. +61422334489
  From: env.TWILIO_FROM_NUMBER,  // your Twilio number or AU Sender ID
  Body: smsBody
});

No prefix manipulation in the Worker — E.164 is assembled entirely on the client, validated on the Worker, then passed clean to Twilio.


Acceptance criteria

  1. Page loads → /api/geo called → dial code selector pre-fills to visitor's country
  2. AU user enters 0422334489 → assembled as +61422334489 → SMS received ✓
  3. AU user enters 422334489 (no leading zero) → assembled as +61422334489 → SMS received ✓
  4. US user with +1 pre-filled, enters 5550001234 → assembled as +15550001234 → Twilio accepts ✓
  5. Invalid input (letters, too short) → validation error shown before submission
  6. Server rejects malformed E.164 with 400 (belt and suspenders)
  7. SMS delivers to Tim's mobile (+61422334489) in test run

Deferred (do not build this session)

  • Country list beyond the ~20 listed above
  • Phone number formatting masks per country
  • RTOpacks referral tracking
  • Alphanumeric Sender ID AU registration (deadline July 2026)