// Server-side Node.js 22+ example. Rename to send-mms.mjs; run with node. // Running this sends one chargeable MMS to your explicitly configured recipient. // The media URL must serve your approved immutable image over HTTPS. import { pathToFileURL } from 'node:url'; export async function sendMms(env = process.env, fetcher = fetch) { function required(name) { if (!env[name]) throw new Error(`Set ${name}.`); return env[name]; } const base = new URL(required('MMS_EXPRESS_BASE_URL')); if (base.protocol !== 'https:' || base.username || base.password || base.pathname !== '/' || base.search || base.hash) throw new Error('Use the approved HTTPS API origin, without /api/v1.'); const key = required('MMS_EXPRESS_API_KEY'); const to = required('MMS_EXPRESS_TO'); if (!/^\+27\d{9}$/.test(to)) throw new Error('Use your authorised +27 recipient.'); const media = new URL(required('MMS_EXPRESS_MEDIA_URL')); if (media.protocol !== 'https:' || media.username || media.password) throw new Error('Use an approved HTTPS image URL without embedded credentials.'); const idempotencyKey = required('MMS_EXPRESS_IDEMPOTENCY_KEY'); if (!idempotencyKey.trim() || idempotencyKey.length > 128 || /[\u0000-\u001f\u007f]/.test(idempotencyKey)) throw new Error('The persisted idempotency key must be at most 128 characters without controls.'); const authorization = { Authorization: `Bearer ${key}` }; // No automatic retries. On an unknown outcome, keep this exact payload and key. const response = await fetcher(new URL('/api/v1/messages', base), { method: 'POST', redirect: 'error', signal: AbortSignal.timeout(60_000), headers: { ...authorization, 'Content-Type': 'application/json', 'Idempotency-Key': idempotencyKey }, body: JSON.stringify({ to, text: 'Your September statement is attached.', media: [{ url: media.href }] }) }); if (response.status !== 202) throw new Error(`Submission HTTP ${response.status}. Review the response securely before retrying.`); const accepted = await response.json(); if (!/^msg_[0-9a-f]{32}$/i.test(accepted.id)) throw new Error('Unexpected message ID. Reconcile using the same request key.'); // Persist accepted.id in your application's durable business-request record here. const statusResponse = await fetcher(new URL(`/api/v1/messages/${accepted.id}`, base), { headers: authorization, redirect: 'error', signal: AbortSignal.timeout(30_000) }); if (statusResponse.status !== 200) throw new Error(`Status HTTP ${statusResponse.status}; keep accepted ID ${accepted.id}.`); const status = await statusResponse.json(); return { id: accepted.id, status: status.status }; // sent is not a read receipt. } if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { sendMms().then(result => console.log(JSON.stringify(result))) .catch(error => { console.error(error.message); process.exitCode = 1; }); }