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:
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 — 0422334489 → 422334489 → +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):
- Accept
mobileas a pre-assembled E.164 string from the client - 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 });
}
- 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¶
- Page loads →
/api/geocalled → dial code selector pre-fills to visitor's country - AU user enters
0422334489→ assembled as+61422334489→ SMS received ✓ - AU user enters
422334489(no leading zero) → assembled as+61422334489→ SMS received ✓ - US user with +1 pre-filled, enters
5550001234→ assembled as+15550001234→ Twilio accepts ✓ - Invalid input (letters, too short) → validation error shown before submission
- Server rejects malformed E.164 with 400 (belt and suspenders)
- 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)