true, CURLOPT_FOLLOWLOCATION => false, CURLOPT_CONNECTTIMEOUT => 10, CURLOPT_TIMEOUT => 60, CURLOPT_PROTOCOLS => CURLPROTO_HTTPS, CURLOPT_HTTPHEADER => $headers, ]); if ($payload !== null) curl_setopt_array($handle, [CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR)]); $body = curl_exec($handle); $status = (int) curl_getinfo($handle, CURLINFO_RESPONSE_CODE); $failed = $body === false; curl_close($handle); if ($failed) throw new RuntimeException('Connection failed; the submission outcome may be unknown. Keep the exact request and its key.'); $expected = $payload === null ? 200 : 202; if ($status !== $expected) throw new RuntimeException("HTTP $status. Review the response securely; do not blindly create a new request."); return json_decode($body, true, 512, JSON_THROW_ON_ERROR); } try { $base = rtrim(requiredEnv('MMS_EXPRESS_BASE_URL'), '/'); $origin = parse_url($base); if ($origin === false || ($origin['scheme'] ?? '') !== 'https' || empty($origin['host']) || isset($origin['user'], $origin['pass']) || isset($origin['user']) || isset($origin['query']) || isset($origin['fragment']) || !empty($origin['path'])) throw new RuntimeException('Use the approved HTTPS API origin, without /api/v1.'); $key = requiredEnv('MMS_EXPRESS_API_KEY'); $to = requiredEnv('MMS_EXPRESS_TO'); if (!preg_match('/^\+27[0-9]{9}$/D', $to)) throw new RuntimeException('Use your authorised +27 recipient.'); $mediaUrl = requiredEnv('MMS_EXPRESS_MEDIA_URL'); $media = parse_url($mediaUrl); if ($media === false || ($media['scheme'] ?? '') !== 'https' || empty($media['host']) || isset($media['user'])) throw new RuntimeException('Use an approved HTTPS image URL without embedded credentials.'); $requestKey = requiredEnv('MMS_EXPRESS_IDEMPOTENCY_KEY'); if (trim($requestKey) === '' || strlen($requestKey) > 128 || preg_match('/[\x00-\x1f\x7f]/', $requestKey)) throw new RuntimeException('Invalid idempotency key.'); $authorization = 'Authorization: Bearer ' . $key; // No automatic POST retry. Persist and reuse this exact request/key on uncertainty. $accepted = requestJson($base . '/api/v1/messages', [$authorization, 'Content-Type: application/json', 'Idempotency-Key: ' . $requestKey], [ 'to' => $to, 'text' => 'Your September statement is attached.', 'media' => [['url' => $mediaUrl]], ]); $id = $accepted['id'] ?? ''; if (!preg_match('/^msg_[0-9a-f]{32}$/iD', $id)) throw new RuntimeException('Unexpected message ID; reconcile with the same request key.'); // Persist $id in your business-request record before doing more work. $status = requestJson($base . '/api/v1/messages/' . rawurlencode($id), [$authorization]); echo json_encode(['id' => $id, 'status' => $status['status']], JSON_THROW_ON_ERROR) . PHP_EOL; } catch (Throwable $error) { fwrite(STDERR, $error->getMessage() . PHP_EOL); exit(1); }