#!/usr/bin/env bash # Server-side example. Requires bash, curl and jq. Sends ONE chargeable MMS # only when you run it with your own approved account, recipient and image. # Keep this key and the EXACT same file/request when recovering a timed-out POST. set -euo pipefail : "${MMS_EXPRESS_BASE_URL:?Set the approved HTTPS API origin, without /api/v1}" : "${MMS_EXPRESS_API_KEY:?Set a server-side key with messages:send and messages:read}" : "${MMS_EXPRESS_TO:?Set your authorised recipient in +27 international format}" : "${MMS_EXPRESS_IMAGE_PATH:?Set the path to your approved JPEG image}" : "${MMS_EXPRESS_IDEMPOTENCY_KEY:?Set a persisted unique business request ID (max 128 characters)}" [[ "$MMS_EXPRESS_BASE_URL" =~ ^https://[^/@?#[:space:]]+/?$ ]] || { echo 'Use an approved HTTPS origin.' >&2; exit 1; } [[ "$MMS_EXPRESS_TO" =~ ^\+27[0-9]{9}$ ]] || { echo 'Use an authorised +27 recipient.' >&2; exit 1; } [[ -f "$MMS_EXPRESS_IMAGE_PATH" ]] || { echo 'Image file not found.' >&2; exit 1; } [[ -n "${MMS_EXPRESS_IDEMPOTENCY_KEY//[[:space:]]/}" && ${#MMS_EXPRESS_IDEMPOTENCY_KEY} -le 128 && ! "$MMS_EXPRESS_IDEMPOTENCY_KEY" =~ [[:cntrl:]] ]] || { echo 'Invalid idempotency key.' >&2; exit 1; } base="${MMS_EXPRESS_BASE_URL%/}" work="$(mktemp -d)" trap 'rm -rf "$work"' EXIT chmod 700 "$work" # A private temporary copy gives curl a fixed, safe multipart filename. cp -- "$MMS_EXPRESS_IMAGE_PATH" "$work/statement.jpg" # No redirects and no automatic POST retries. A connection timeout is an unknown # submission outcome: repeat this exact request with the same persisted key. http_status="$(curl --silent --show-error --proto '=https' --connect-timeout 10 --max-time 60 \ --output "$work/accepted.json" --write-out '%{http_code}' \ --request POST "$base/api/v1/messages" \ --header "Authorization: Bearer $MMS_EXPRESS_API_KEY" \ --header "Idempotency-Key: $MMS_EXPRESS_IDEMPOTENCY_KEY" \ --form-string "to=$MMS_EXPRESS_TO" \ --form-string 'text=Your September statement is attached.' \ --form "files=@$work/statement.jpg;type=image/jpeg")" [[ "$http_status" == 202 ]] || { echo "Submission HTTP $http_status. Review securely; do not blindly create a new request." >&2; exit 1; } message_id="$(jq -er '.id | select(test("^msg_[0-9a-fA-F]{32}$"))' "$work/accepted.json")" # Request accepted does not mean delivery confirmed. Retrieve the recorded status. http_status="$(curl --silent --show-error --proto '=https' --connect-timeout 10 --max-time 30 \ --output "$work/status.json" --write-out '%{http_code}' \ "$base/api/v1/messages/$message_id" \ --header "Authorization: Bearer $MMS_EXPRESS_API_KEY")" [[ "$http_status" == 200 ]] || { echo "Status HTTP $http_status. Keep the accepted message ID for reconciliation." >&2; exit 1; } # Print only the selected fields. Keep message bodies and account data out of logs. jq '{id, status}' "$work/status.json"